1 /* nvd3 version 1.7.0(https://github.com/liquidpele/nvd3) 2014-12-13 */
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 = false; //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
948 Else return what was passed in
950 nv.utils.getColor = function(color) {
951 //if you pass in nothing, get default colors back
952 if (!arguments.length) {
953 return nv.utils.defaultColor();
955 //if passed an array, wrap it in a function
956 } else if(color instanceof Array) {
957 return function(d, i) { return d.color || color[i % color.length]; };
959 //if passed a function, return the function, or whatever it may be
960 //external libs, such as angularjs-nvd3-directives use this
962 //can't really help it if someone passes rubbish as color
969 Default color chooser uses the index of an object as before.
971 nv.utils.defaultColor = function() {
972 var colors = d3.scale.category20().range();
973 return function(d, i) {
974 return d.color || colors[i % colors.length]
980 Returns a color function that takes the result of 'getKey' for each series and
981 looks for a corresponding color from the dictionary
983 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
984 // use default series.key if getKey is undefined
985 getKey = getKey || function(series) { return series.key };
986 defaultColors = defaultColors || d3.scale.category20().range();
988 // start at end of default color list and walk back to index 0
989 var defIndex = defaultColors.length;
991 return function(series, index) {
992 var key = getKey(series);
993 if (typeof dictionary[key] === 'function') {
994 return dictionary[key]();
995 } else if (dictionary[key] !== undefined) {
996 return dictionary[key];
998 // no match in dictionary, use a default color
1000 // used all the default colors, start over
1001 defIndex = defaultColors.length;
1003 defIndex = defIndex - 1;
1004 return defaultColors[defIndex];
1011 From the PJAX example on d3js.org, while this is not really directly needed
1012 it's a very cool method for doing pjax, I may expand upon it a little bit,
1013 open to suggestions on anything that may be useful
1015 nv.utils.pjax = function(links, content) {
1017 var load = function(href) {
1018 d3.html(href, function(fragment) {
1019 var target = d3.select(content).node();
1020 target.parentNode.replaceChild(
1021 d3.select(fragment).select(content).node(),
1023 nv.utils.pjax(links, content);
1027 d3.selectAll(links).on("click", function() {
1028 history.pushState(this.href, this.textContent, this.href);
1030 d3.event.preventDefault();
1033 d3.select(window).on("popstate", function() {
1034 if (d3.event.state) {
1035 load(d3.event.state);
1042 For when we want to approximate the width in pixels for an SVG:text element.
1043 Most common instance is when the element is in a display:none; container.
1044 Forumla is : text.length * font-size * constant_factor
1046 nv.utils.calcApproxTextWidth = function (svgTextElem) {
1047 if (typeof svgTextElem.style === 'function'
1048 && typeof svgTextElem.text === 'function') {
1050 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
1051 var textLength = svgTextElem.text().length;
1052 return textLength * fontSize * 0.5;
1059 Numbers that are undefined, null or NaN, convert them to zeros.
1061 nv.utils.NaNtoZero = function(n) {
1062 if (typeof n !== 'number'
1066 || n === -Infinity) {
1074 Add a way to watch for d3 transition ends to d3
1076 d3.selection.prototype.watchTransition = function(renderWatch){
1077 var args = [this].concat([].slice.call(arguments, 1));
1078 return renderWatch.transition.apply(renderWatch, args);
1083 Helper object to watch when d3 has rendered something
1085 nv.utils.renderWatch = function(dispatch, duration) {
1086 if (!(this instanceof nv.utils.renderWatch)) {
1087 return new nv.utils.renderWatch(dispatch, duration);
1090 var _duration = duration !== undefined ? duration : 250;
1091 var renderStack = [];
1094 this.models = function(models) {
1095 models = [].slice.call(arguments, 0);
1096 models.forEach(function(model){
1097 model.__rendered = false;
1099 m.dispatch.on('renderEnd', function(arg){
1100 m.__rendered = true;
1101 self.renderEnd('model');
1105 if (renderStack.indexOf(model) < 0) {
1106 renderStack.push(model);
1112 this.reset = function(duration) {
1113 if (duration !== undefined) {
1114 _duration = duration;
1119 this.transition = function(selection, args, duration) {
1120 args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1122 if (args.length > 1) {
1123 duration = args.pop();
1125 duration = _duration !== undefined ? _duration : 250;
1127 selection.__rendered = false;
1129 if (renderStack.indexOf(selection) < 0) {
1130 renderStack.push(selection);
1133 if (duration === 0) {
1134 selection.__rendered = true;
1135 selection.delay = function() { return this; };
1136 selection.duration = function() { return this; };
1139 if (selection.length === 0) {
1140 selection.__rendered = true;
1141 } else if (selection.every( function(d){ return !d.length; } )) {
1142 selection.__rendered = true;
1144 selection.__rendered = false;
1151 .each(function(){ ++n; })
1152 .each('end', function(d, i) {
1154 selection.__rendered = true;
1155 self.renderEnd.apply(this, args);
1161 this.renderEnd = function() {
1162 if (renderStack.every( function(d){ return d.__rendered; } )) {
1163 renderStack.forEach( function(d){ d.__rendered = false; });
1164 dispatch.renderEnd.apply(this, arguments);
1172 Takes multiple objects and combines them into the first one (dst)
1173 example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
1174 gives: {a: 2, b: 3, c: 4}
1176 nv.utils.deepExtend = function(dst){
1177 var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1178 sources.forEach(function(source) {
1179 for (key in source) {
1180 var isArray = dst[key] instanceof Array;
1181 var isObject = typeof dst[key] === 'object';
1182 var srcObj = typeof source[key] === 'object';
1184 if (isObject && !isArray && srcObj) {
1185 nv.utils.deepExtend(dst[key], source[key]);
1187 dst[key] = source[key];
1195 state utility object, used to track d3 states in the models
1197 nv.utils.state = function(){
1198 if (!(this instanceof nv.utils.state)) {
1199 return new nv.utils.state();
1203 var _setState = function(){};
1204 var _getState = function(){ return {}; };
1208 this.dispatch = d3.dispatch('change', 'set');
1210 this.dispatch.on('set', function(state){
1211 _setState(state, true);
1214 this.getter = function(fn){
1219 this.setter = function(fn, callback) {
1221 callback = function(){};
1223 _setState = function(state, update){
1232 this.init = function(state){
1234 nv.utils.deepExtend(init, state);
1237 var _set = function(){
1238 var settings = _getState();
1240 if (JSON.stringify(settings) === JSON.stringify(state)) {
1244 for (var key in settings) {
1245 if (state[key] === undefined) {
1248 state[key] = settings[key];
1254 this.update = function(){
1256 _setState(init, false);
1259 if (_set.call(this)) {
1260 this.dispatch.change(state);
1268 Snippet of code you can insert into each nv.models.* to give you the ability to
1275 To enable in the chart:
1276 chart.options = nv.utils.optionsFunc.bind(chart);
1278 nv.utils.optionsFunc = function(args) {
1279 nv.deprecated('nv.utils.optionsFunc');
1281 d3.map(args).forEach((function(key,value) {
1282 if (typeof this[key] === "function") {
1292 numTicks: requested number of ticks
1293 data: the chart data
1295 returns the number of ticks to actually use on X axis, based on chart data
1296 to avoid duplicate ticks with the same value
1298 nv.utils.calcTicksX = function(numTicks, data) {
1299 // find max number of values from all data streams
1302 for (i; i < data.length; i += 1) {
1303 var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
1304 numValues = stream_len > numValues ? stream_len : numValues;
1306 nv.log("Requested number of ticks: ", numTicks);
1307 nv.log("Calculated max values to be: ", numValues);
1308 // make sure we don't have more ticks than values to avoid duplicates
1309 numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
1310 // make sure we have at least one tick
1311 numTicks = numTicks < 1 ? 1 : numTicks;
1312 // make sure it's an integer
1313 numTicks = Math.floor(numTicks);
1314 nv.log("Calculating tick count as: ", numTicks);
1320 returns number of ticks to actually use on Y axis, based on chart data
1322 nv.utils.calcTicksY = function(numTicks, data) {
1323 // currently uses the same logic but we can adjust here if needed later
1324 return nv.utils.calcTicksX(numTicks, data);
1329 Add a particular option from an options object onto chart
1330 Options exposed on a chart are a getter/setter function that returns chart
1331 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
1333 option objects should be generated via Object.create() to provide
1334 the option of manipulating data via get/set functions.
1336 nv.utils.initOption = function(chart, name) {
1337 // if it's a call option, just call it directly, otherwise do get/set
1338 if (chart._calls && chart._calls[name]) {
1339 chart[name] = chart._calls[name];
1341 chart[name] = function (_) {
1342 if (!arguments.length) return chart._options[name];
1343 chart._options[name] = _;
1351 Add all options in an options object to the chart
1353 nv.utils.initOptions = function(chart) {
1354 var ops = Object.getOwnPropertyNames(chart._options || {});
1355 var calls = Object.getOwnPropertyNames(chart._calls || {});
1356 ops = ops.concat(calls);
1357 for (var i in ops) {
1358 nv.utils.initOption(chart, ops[i]);
1364 Inherit options from a D3 object
1365 d3.rebind makes calling the function on target actually call it on source
1366 Also use _d3options so we can track what we inherit for documentation and chained inheritance
1368 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
1369 target._d3options = oplist.concat(target._d3options || []);
1370 oplist.unshift(d3_source);
1371 oplist.unshift(target);
1372 d3.rebind.apply(this, oplist);
1377 Remove duplicates from an array
1379 nv.utils.arrayUnique = function(a) {
1380 return a.sort().filter(function(item, pos) {
1381 return !pos || item != a[pos - 1];
1387 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
1388 Necessary since d3 doesn't let you extend its list -_-
1389 Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
1391 nv.utils.symbolMap = d3.map();
1395 Replaces d3.svg.symbol so that we can look both there and our own map
1397 nv.utils.symbol = function() {
1400 function symbol(d,i) {
1401 var t = type.call(this,d,i);
1402 var s = size.call(this,d,i);
1403 if (d3.svg.symbolTypes.indexOf(t) !== -1) {
1404 return d3.svg.symbol().type(t).size(s)();
1406 return nv.utils.symbolMap.get(t)(s);
1409 symbol.type = function(_) {
1410 if (!arguments.length) return type;
1411 type = d3.functor(_);
1414 symbol.size = function(_) {
1415 if (!arguments.length) return size;
1416 size = d3.functor(_);
1424 Inherit option getter/setter functions from source to target
1425 d3.rebind makes calling the function on target actually call it on source
1426 Also track via _inherited and _d3options so we can track what we inherit
1427 for documentation generation purposes and chained inheritance
1429 nv.utils.inheritOptions = function(target, source) {
1430 // inherit all the things
1431 var ops = Object.getOwnPropertyNames(source._options || {});
1432 var calls = Object.getOwnPropertyNames(source._calls || {});
1433 var inherited = source._inherited || [];
1434 var d3ops = source._d3options || [];
1435 var args = ops.concat(calls).concat(inherited).concat(d3ops);
1436 args.unshift(source);
1437 args.unshift(target);
1438 d3.rebind.apply(this, args);
1439 // pass along the lists to keep track of them, don't allow duplicates
1440 target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
1441 target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
1446 Runs common initialize code on the svg before the chart builds
1448 nv.utils.initSVG = function(svg) {
1449 svg.classed({'nvd3-svg':true});
1450 };nv.models.axis = function() {
1453 //============================================================
1454 // Public Variables with Default Settings
1455 //------------------------------------------------------------
1457 var axis = d3.svg.axis();
1458 var scale = d3.scale.linear();
1460 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1461 , width = 75 //only used for tickLabel currently
1462 , height = 60 //only used for tickLabel currently
1463 , axisLabelText = null
1464 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1465 , highlightZero = true
1467 , rotateYLabel = true
1468 , staggerLabels = false
1471 , axisLabelDistance = 0
1473 , dispatch = d3.dispatch('renderEnd')
1474 , axisRendered = false
1475 , maxMinRendered = false
1480 .tickFormat(function(d) { return d })
1483 //============================================================
1484 // Private Variables
1485 //------------------------------------------------------------
1488 var renderWatch = nv.utils.renderWatch(dispatch, duration);
1490 function chart(selection) {
1491 renderWatch.reset();
1492 selection.each(function(data) {
1493 var container = d3.select(this);
1494 nv.utils.initSVG(container);
1496 // Setup containers and skeleton of chart
1497 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1498 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1499 var gEnter = wrapEnter.append('g');
1500 var g = wrap.select('g')
1504 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1505 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1507 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1508 g.watchTransition(renderWatch, 'axis').call(axis);
1510 scale0 = scale0 || axis.scale();
1512 var fmt = axis.tickFormat();
1514 fmt = scale0.tickFormat();
1517 var axisLabel = g.selectAll('text.nv-axislabel')
1518 .data([axisLabelText || null]);
1519 axisLabel.exit().remove();
1521 switch (axis.orient()) {
1523 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1525 if (scale.range().length < 2) {
1527 } else if (scale.range().length === 2) {
1528 w = scale.range()[1];
1530 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1533 .attr('text-anchor', 'middle')
1537 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1538 .data(scale.domain());
1539 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1540 axisMaxMin.exit().remove();
1542 .attr('transform', function(d,i) {
1543 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
1546 .attr('dy', '-0.5em')
1547 .attr('y', -axis.tickPadding())
1548 .attr('text-anchor', 'middle')
1549 .text(function(d,i) {
1551 return ('' + v).match('NaN') ? '' : v;
1553 axisMaxMin.watchTransition(renderWatch, 'min-max top')
1554 .attr('transform', function(d,i) {
1555 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
1560 var xLabelMargin = axisLabelDistance + 36;
1561 var maxTextWidth = 30;
1562 var xTicks = g.selectAll('g').select("text");
1563 if (rotateLabels%360) {
1564 //Calculate the longest xTick width
1565 xTicks.each(function(d,i){
1566 var width = this.getBoundingClientRect().width;
1567 if(width > maxTextWidth) maxTextWidth = width;
1569 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1570 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1571 var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1574 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1575 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1577 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1579 if (scale.range().length < 2) {
1581 } else if (scale.range().length === 2) {
1582 w = scale.range()[1];
1584 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1587 .attr('text-anchor', 'middle')
1588 .attr('y', xLabelMargin)
1591 //if (showMaxMin && !isOrdinal) {
1592 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1593 //.data(scale.domain())
1594 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1595 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1596 axisMaxMin.exit().remove();
1598 .attr('transform', function(d,i) {
1599 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1602 .attr('dy', '.71em')
1603 .attr('y', axis.tickPadding())
1604 .attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1605 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1606 .text(function(d,i) {
1608 return ('' + v).match('NaN') ? '' : v;
1610 axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
1611 .attr('transform', function(d,i) {
1612 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1617 .attr('transform', function(d,i) { return 'translate(0,' + nv.utils.NaNtoZero((i % 2 == 0 ? '0' : '12')) + ')' });
1621 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1623 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1624 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1625 .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
1626 .attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1628 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1629 .data(scale.domain());
1630 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1631 .style('opacity', 0);
1632 axisMaxMin.exit().remove();
1634 .attr('transform', function(d,i) {
1635 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
1638 .attr('dy', '.32em')
1640 .attr('x', axis.tickPadding())
1641 .style('text-anchor', 'start')
1642 .text(function(d,i) {
1644 return ('' + v).match('NaN') ? '' : v;
1646 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1647 .attr('transform', function(d,i) {
1648 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1651 .style('opacity', 1);
1656 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1657 var yTicks = g.selectAll('g').select("text");
1658 yTicks.each(function(d,i){
1659 var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
1660 if(labelPadding > width) width = labelPadding;
1663 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1665 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1666 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1667 .attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 25 - (axisLabelDistance || 0)) : -10)
1668 .attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1670 var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1671 .data(scale.domain());
1672 axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1673 .style('opacity', 0);
1674 axisMaxMin.exit().remove();
1676 .attr('transform', function(d,i) {
1677 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
1680 .attr('dy', '.32em')
1682 .attr('x', -axis.tickPadding())
1683 .attr('text-anchor', 'end')
1684 .text(function(d,i) {
1686 return ('' + v).match('NaN') ? '' : v;
1688 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1689 .attr('transform', function(d,i) {
1690 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1693 .style('opacity', 1);
1697 axisLabel.text(function(d) { return d });
1699 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1700 //check if max and min overlap other values, if so, hide the values that overlap
1701 g.selectAll('g') // the g's wrapping each tick
1702 .each(function(d,i) {
1703 d3.select(this).select('text').attr('opacity', 1);
1704 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!
1705 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1706 d3.select(this).attr('opacity', 0);
1708 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1712 //if Max and Min = 0 only show min, Issue #281
1713 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
1714 wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
1720 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1721 var maxMinRange = [];
1722 wrap.selectAll('g.nv-axisMaxMin')
1723 .each(function(d,i) {
1725 if (i) // i== 1, max position
1726 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)
1727 else // i==0, min position
1728 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
1730 if (i) // i== 1, max position
1731 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)
1732 else // i==0, min position
1733 maxMinRange.push(scale(d) + 4);
1736 // the g's wrapping each tick
1737 g.selectAll('g').each(function(d,i) {
1738 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1739 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1740 d3.select(this).remove();
1742 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1747 //highlight zero line ... Maybe should not be an option and should just be in CSS?
1748 if (highlightZero) {
1749 g.selectAll('.tick')
1750 .filter(function (d) {
1751 return !parseFloat(Math.round(this.__data__ * 100000) / 1000000) && (this.__data__ !== undefined)
1752 }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1753 .classed('zero', true);
1755 //store old scales for use in transitions on update
1756 scale0 = scale.copy();
1760 renderWatch.renderEnd('axis immediate');
1764 //============================================================
1765 // Expose Public Variables
1766 //------------------------------------------------------------
1768 // expose chart's sub-components
1770 chart.dispatch = dispatch;
1772 chart.options = nv.utils.optionsFunc.bind(chart);
1773 chart._options = Object.create({}, {
1774 // simple options, just get/set the necessary values
1775 axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
1776 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
1777 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
1778 rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
1779 highlightZero: {get: function(){return highlightZero;}, set: function(_){highlightZero=_;}},
1780 showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
1781 axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
1782 height: {get: function(){return height;}, set: function(_){height=_;}},
1783 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
1784 width: {get: function(){return width;}, set: function(_){width=_;}},
1786 // options that require extra logic in the setter
1787 margin: {get: function(){return margin;}, set: function(_){
1788 margin.top = _.top !== undefined ? _.top : margin.top;
1789 margin.right = _.right !== undefined ? _.right : margin.right;
1790 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
1791 margin.left = _.left !== undefined ? _.left : margin.left;
1793 duration: {get: function(){return duration;}, set: function(_){
1795 renderWatch.reset(duration);
1797 scale: {get: function(){return scale;}, set: function(_){
1800 isOrdinal = typeof scale.rangeBands === 'function';
1801 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1805 nv.utils.initOptions(chart);
1806 nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
1807 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1812 // Chart design based on the recommendations of Stephen Few. Implementation
1813 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1814 // http://projects.instantcognition.com/protovis/bulletchart/
1816 nv.models.bullet = function() {
1819 //============================================================
1820 // Public Variables with Default Settings
1821 //------------------------------------------------------------
1823 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1824 , orient = 'left' // TODO top & bottom
1826 , ranges = function(d) { return d.ranges }
1827 , markers = function(d) { return d.markers ? d.markers : [0] }
1828 , measures = function(d) { return d.measures }
1829 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1830 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
1831 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
1832 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1836 , color = nv.utils.getColor(['#1f77b4'])
1837 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1840 function chart(selection) {
1841 selection.each(function(d, i) {
1842 var availableWidth = width - margin.left - margin.right,
1843 availableHeight = height - margin.top - margin.bottom,
1844 container = d3.select(this);
1845 nv.utils.initSVG(container);
1847 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1848 markerz = markers.call(this, d, i).slice().sort(d3.descending),
1849 measurez = measures.call(this, d, i).slice().sort(d3.descending),
1850 rangeLabelz = rangeLabels.call(this, d, i).slice(),
1851 markerLabelz = markerLabels.call(this, d, i).slice(),
1852 measureLabelz = measureLabels.call(this, d, i).slice();
1855 // Compute the new x-scale.
1856 var x1 = d3.scale.linear()
1857 .domain( d3.extent(d3.merge([forceX, rangez])) )
1858 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1860 // Retrieve the old x-scale, if this is an update.
1861 var x0 = this.__chart__ || d3.scale.linear()
1862 .domain([0, Infinity])
1865 // Stash the new scale.
1866 this.__chart__ = x1;
1868 var rangeMin = d3.min(rangez), //rangez[2]
1869 rangeMax = d3.max(rangez), //rangez[0]
1870 rangeAvg = rangez[1];
1872 // Setup containers and skeleton of chart
1873 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1874 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1875 var gEnter = wrapEnter.append('g');
1876 var g = wrap.select('g');
1878 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1879 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1880 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1881 gEnter.append('rect').attr('class', 'nv-measure');
1882 gEnter.append('path').attr('class', 'nv-markerTriangle');
1884 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1886 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1887 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1888 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1889 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 g.select('rect.nv-rangeAvg')
1898 .attr('height', availableHeight)
1899 .attr('width', w1(rangeAvg))
1900 .attr('x', xp1(rangeAvg))
1903 g.select('rect.nv-rangeMin')
1904 .attr('height', availableHeight)
1905 .attr('width', w1(rangeMax))
1906 .attr('x', xp1(rangeMax))
1907 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1908 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1909 .datum(rangeMax > 0 ? rangeMin : rangeMax)
1911 g.select('rect.nv-measure')
1912 .style('fill', color)
1913 .attr('height', availableHeight / 3)
1914 .attr('y', availableHeight / 3)
1915 .attr('width', measurez < 0 ?
1916 x1(0) - x1(measurez[0])
1917 : x1(measurez[0]) - x1(0))
1918 .attr('x', xp1(measurez))
1919 .on('mouseover', function() {
1920 dispatch.elementMouseover({
1922 label: measureLabelz[0] || 'Current',
1923 pos: [x1(measurez[0]), availableHeight/2]
1926 .on('mouseout', function() {
1927 dispatch.elementMouseout({
1929 label: measureLabelz[0] || 'Current'
1933 var h3 = availableHeight / 6;
1935 g.selectAll('path.nv-markerTriangle')
1936 .attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1937 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1938 .on('mouseover', function() {
1939 dispatch.elementMouseover({
1941 label: markerLabelz[0] || 'Previous',
1942 pos: [x1(markerz[0]), availableHeight/2]
1945 .on('mouseout', function() {
1946 dispatch.elementMouseout({
1948 label: markerLabelz[0] || 'Previous'
1952 g.selectAll('path.nv-markerTriangle').remove();
1955 wrap.selectAll('.nv-range')
1956 .on('mouseover', function(d,i) {
1957 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1959 dispatch.elementMouseover({
1962 pos: [x1(d), availableHeight/2]
1965 .on('mouseout', function(d,i) {
1966 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1968 dispatch.elementMouseout({
1978 //============================================================
1979 // Expose Public Variables
1980 //------------------------------------------------------------
1982 chart.dispatch = dispatch;
1983 chart.options = nv.utils.optionsFunc.bind(chart);
1985 chart._options = Object.create({}, {
1986 // simple options, just get/set the necessary values
1987 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
1988 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
1989 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
1990 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
1991 width: {get: function(){return width;}, set: function(_){width=_;}},
1992 height: {get: function(){return height;}, set: function(_){height=_;}},
1993 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
1995 // options that require extra logic in the setter
1996 margin: {get: function(){return margin;}, set: function(_){
1997 margin.top = _.top !== undefined ? _.top : margin.top;
1998 margin.right = _.right !== undefined ? _.right : margin.right;
1999 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2000 margin.left = _.left !== undefined ? _.left : margin.left;
2002 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2004 reverse = orient == 'right' || orient == 'bottom';
2006 color: {get: function(){return color;}, set: function(_){
2007 color = nv.utils.getColor(_);
2011 nv.utils.initOptions(chart);
2017 // Chart design based on the recommendations of Stephen Few. Implementation
2018 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2019 // http://projects.instantcognition.com/protovis/bulletchart/
2020 nv.models.bulletChart = function() {
2023 //============================================================
2024 // Public Variables with Default Settings
2025 //------------------------------------------------------------
2027 var bullet = nv.models.bullet()
2030 var orient = 'left' // TODO top & bottom
2032 , margin = {top: 5, right: 40, bottom: 20, left: 120}
2033 , ranges = function(d) { return d.ranges }
2034 , markers = function(d) { return d.markers ? d.markers : [0] }
2035 , measures = function(d) { return d.measures }
2040 , tooltip = function(key, x, y, e, graph) {
2041 return '<h3>' + x + '</h3>' +
2044 , noData = 'No Data Available.'
2045 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2048 //============================================================
2049 // Private Variables
2050 //------------------------------------------------------------
2052 var showTooltip = function(e, offsetElement) {
2053 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
2054 top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
2055 content = tooltip(e.key, e.label, e.value, e, chart);
2057 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
2060 function chart(selection) {
2061 selection.each(function(d, i) {
2062 var container = d3.select(this);
2063 nv.utils.initSVG(container);
2065 var availableWidth = (width || parseInt(container.style('width')) || 960)
2066 - margin.left - margin.right,
2067 availableHeight = height - margin.top - margin.bottom,
2070 chart.update = function() { chart(selection) };
2071 chart.container = this;
2073 // Display No Data message if there's nothing to show.
2074 if (!d || !ranges.call(this, d, i)) {
2075 var noDataText = container.selectAll('.nv-noData').data([noData]);
2077 noDataText.enter().append('text')
2078 .attr('class', 'nvd3 nv-noData')
2079 .attr('dy', '-.7em')
2080 .style('text-anchor', 'middle');
2083 .attr('x', margin.left + availableWidth / 2)
2084 .attr('y', 18 + margin.top + availableHeight / 2)
2085 .text(function(d) { return d });
2089 container.selectAll('.nv-noData').remove();
2092 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2093 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2094 measurez = measures.call(this, d, i).slice().sort(d3.descending);
2096 // Setup containers and skeleton of chart
2097 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2098 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2099 var gEnter = wrapEnter.append('g');
2100 var g = wrap.select('g');
2102 gEnter.append('g').attr('class', 'nv-bulletWrap');
2103 gEnter.append('g').attr('class', 'nv-titles');
2105 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2107 // Compute the new x-scale.
2108 var x1 = d3.scale.linear()
2109 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
2110 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2112 // Retrieve the old x-scale, if this is an update.
2113 var x0 = this.__chart__ || d3.scale.linear()
2114 .domain([0, Infinity])
2117 // Stash the new scale.
2118 this.__chart__ = x1;
2120 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2121 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2123 var title = gEnter.select('.nv-titles').append('g')
2124 .attr('text-anchor', 'end')
2125 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2126 title.append('text')
2127 .attr('class', 'nv-title')
2128 .text(function(d) { return d.title; });
2130 title.append('text')
2131 .attr('class', 'nv-subtitle')
2133 .text(function(d) { return d.subtitle; });
2136 .width(availableWidth)
2137 .height(availableHeight)
2139 var bulletWrap = g.select('.nv-bulletWrap');
2140 d3.transition(bulletWrap).call(bullet);
2142 // Compute the tick format.
2143 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2145 // Update the tick groups.
2146 var tick = g.selectAll('g.nv-tick')
2147 .data(x1.ticks( availableWidth / 50 ), function(d) {
2148 return this.textContent || format(d);
2151 // Initialize the ticks with the old scale, x0.
2152 var tickEnter = tick.enter().append('g')
2153 .attr('class', 'nv-tick')
2154 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2155 .style('opacity', 1e-6);
2157 tickEnter.append('line')
2158 .attr('y1', availableHeight)
2159 .attr('y2', availableHeight * 7 / 6);
2161 tickEnter.append('text')
2162 .attr('text-anchor', 'middle')
2164 .attr('y', availableHeight * 7 / 6)
2167 // Transition the updating ticks to the new scale, x1.
2168 var tickUpdate = d3.transition(tick)
2169 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2170 .style('opacity', 1);
2172 tickUpdate.select('line')
2173 .attr('y1', availableHeight)
2174 .attr('y2', availableHeight * 7 / 6);
2176 tickUpdate.select('text')
2177 .attr('y', availableHeight * 7 / 6);
2179 // Transition the exiting ticks to the new scale, x1.
2180 d3.transition(tick.exit())
2181 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2182 .style('opacity', 1e-6)
2185 //============================================================
2186 // Event Handling/Dispatching (in chart's scope)
2187 //------------------------------------------------------------
2189 dispatch.on('tooltipShow', function(e) {
2191 if (tooltips) showTooltip(e, that.parentNode);
2200 //============================================================
2201 // Event Handling/Dispatching (out of chart's scope)
2202 //------------------------------------------------------------
2204 bullet.dispatch.on('elementMouseover.tooltip', function(e) {
2205 dispatch.tooltipShow(e);
2208 bullet.dispatch.on('elementMouseout.tooltip', function(e) {
2209 dispatch.tooltipHide(e);
2212 dispatch.on('tooltipHide', function() {
2213 if (tooltips) nv.tooltip.cleanup();
2216 //============================================================
2217 // Expose Public Variables
2218 //------------------------------------------------------------
2220 chart.bullet = bullet;
2221 chart.dispatch = dispatch;
2222 chart.options = nv.utils.optionsFunc.bind(chart);
2224 chart._options = Object.create({}, {
2225 // simple options, just get/set the necessary values
2226 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
2227 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
2228 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
2229 width: {get: function(){return width;}, set: function(_){width=_;}},
2230 height: {get: function(){return height;}, set: function(_){height=_;}},
2231 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
2232 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
2233 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
2234 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
2236 // options that require extra logic in the setter
2237 margin: {get: function(){return margin;}, set: function(_){
2238 margin.top = _.top !== undefined ? _.top : margin.top;
2239 margin.right = _.right !== undefined ? _.right : margin.right;
2240 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2241 margin.left = _.left !== undefined ? _.left : margin.left;
2243 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2245 reverse = orient == 'right' || orient == 'bottom';
2249 nv.utils.inheritOptions(chart, bullet);
2250 nv.utils.initOptions(chart);
2257 nv.models.cumulativeLineChart = function() {
2260 //============================================================
2261 // Public Variables with Default Settings
2262 //------------------------------------------------------------
2264 var lines = nv.models.line()
2265 , xAxis = nv.models.axis()
2266 , yAxis = nv.models.axis()
2267 , legend = nv.models.legend()
2268 , controls = nv.models.legend()
2269 , interactiveLayer = nv.interactiveGuideline()
2272 var margin = {top: 30, right: 30, bottom: 50, left: 60}
2273 , color = nv.utils.defaultColor()
2279 , rightAlignYAxis = false
2281 , showControls = true
2282 , useInteractiveGuideline = false
2284 , tooltip = function(key, x, y, e, graph) {
2285 return '<h3>' + key + '</h3>' +
2286 '<p>' + y + ' at ' + x + '</p>'
2288 , x //can be accessed via chart.xScale()
2289 , y //can be accessed via chart.yScale()
2291 , state = nv.utils.state()
2292 , defaultState = null
2293 , noData = 'No Data Available.'
2294 , average = function(d) { return d.average }
2295 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
2296 , transitionDuration = 250
2298 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
2302 state.rescaleY = rescaleY;
2309 .orient((rightAlignYAxis) ? 'right' : 'left')
2312 controls.updateState(false);
2314 //============================================================
2315 // Private Variables
2316 //------------------------------------------------------------
2318 var dx = d3.scale.linear()
2319 , index = {i: 0, x: 0}
2320 , renderWatch = nv.utils.renderWatch(dispatch, duration)
2323 var showTooltip = function(e, offsetElement) {
2324 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2325 top = e.pos[1] + ( offsetElement.offsetTop || 0),
2326 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2327 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2328 content = tooltip(e.series.key, x, y, e, chart);
2330 nv.tooltip.show([left, top], content, null, null, offsetElement);
2333 var stateGetter = function(data) {
2336 active: data.map(function(d) { return !d.disabled }),
2343 var stateSetter = function(data) {
2344 return function(state) {
2345 if (state.index !== undefined)
2346 index.i = state.index;
2347 if (state.rescaleY !== undefined)
2348 rescaleY = state.rescaleY;
2349 if (state.active !== undefined)
2350 data.forEach(function(series,i) {
2351 series.disabled = !state.active[i];
2356 function chart(selection) {
2357 renderWatch.reset();
2358 renderWatch.models(lines);
2359 if (showXAxis) renderWatch.models(xAxis);
2360 if (showYAxis) renderWatch.models(yAxis);
2361 selection.each(function(data) {
2362 var container = d3.select(this);
2363 nv.utils.initSVG(container);
2364 container.classed('nv-chart-' + id, true);
2367 var availableWidth = (width || parseInt(container.style('width')) || 960)
2368 - margin.left - margin.right,
2369 availableHeight = (height || parseInt(container.style('height')) || 400)
2370 - margin.top - margin.bottom;
2372 chart.update = function() {
2374 container.call(chart);
2376 container.transition().duration(duration).call(chart)
2378 chart.container = this;
2381 .setter(stateSetter(data), chart.update)
2382 .getter(stateGetter(data))
2385 // DEPRECATED set state.disableddisabled
2386 state.disabled = data.map(function(d) { return !!d.disabled });
2388 if (!defaultState) {
2391 for (key in state) {
2392 if (state[key] instanceof Array)
2393 defaultState[key] = state[key].slice(0);
2395 defaultState[key] = state[key];
2399 var indexDrag = d3.behavior.drag()
2400 .on('dragstart', dragStart)
2401 .on('drag', dragMove)
2402 .on('dragend', dragEnd);
2405 function dragStart(d,i) {
2406 d3.select(chart.container)
2407 .style('cursor', 'ew-resize');
2410 function dragMove(d,i) {
2411 index.x = d3.event.x;
2412 index.i = Math.round(dx.invert(index.x));
2416 function dragEnd(d,i) {
2417 d3.select(chart.container)
2418 .style('cursor', 'auto');
2420 // update state and send stateChange with new index
2421 state.index = index.i;
2422 dispatch.stateChange(state);
2425 // Display No Data message if there's nothing to show.
2426 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2427 var noDataText = container.selectAll('.nv-noData').data([noData]);
2429 noDataText.enter().append('text')
2430 .attr('class', 'nvd3 nv-noData')
2431 .attr('dy', '-.7em')
2432 .style('text-anchor', 'middle');
2435 .attr('x', margin.left + availableWidth / 2)
2436 .attr('y', margin.top + availableHeight / 2)
2437 .text(function(d) { return d });
2441 container.selectAll('.nv-noData').remove();
2449 var seriesDomains = data
2450 .filter(function(series) { return !series.disabled })
2451 .map(function(series,i) {
2452 var initialDomain = d3.extent(series.values, lines.y());
2454 //account for series being disabled when losing 95% or more
2455 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2458 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2459 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2463 var completeDomain = [
2464 d3.min(seriesDomains, function(d) { return d[0] }),
2465 d3.max(seriesDomains, function(d) { return d[1] })
2468 lines.yDomain(completeDomain);
2470 lines.yDomain(null);
2473 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
2474 .range([0, availableWidth])
2477 var data = indexify(index.i, data);
2479 // Setup containers and skeleton of chart
2480 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2481 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2482 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2483 var g = wrap.select('g');
2485 gEnter.append('g').attr('class', 'nv-interactive');
2486 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2487 gEnter.append('g').attr('class', 'nv-y nv-axis');
2488 gEnter.append('g').attr('class', 'nv-background');
2489 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2490 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2491 gEnter.append('g').attr('class', 'nv-legendWrap');
2492 gEnter.append('g').attr('class', 'nv-controlsWrap');
2496 legend.width(availableWidth);
2498 g.select('.nv-legendWrap')
2502 if ( margin.top != legend.height()) {
2503 margin.top = legend.height();
2504 availableHeight = (height || parseInt(container.style('height')) || 400)
2505 - margin.top - margin.bottom;
2508 g.select('.nv-legendWrap')
2509 .attr('transform', 'translate(0,' + (-margin.top) +')')
2514 var controlsData = [
2515 { key: 'Re-scale y-axis', disabled: !rescaleY }
2520 .color(['#444', '#444', '#444'])
2522 .margin({top: 5, right: 0, bottom: 5, left: 20})
2525 g.select('.nv-controlsWrap')
2526 .datum(controlsData)
2527 .attr('transform', 'translate(0,' + (-margin.top) +')')
2531 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2533 if (rightAlignYAxis) {
2534 g.select(".nv-y.nv-axis")
2535 .attr("transform", "translate(" + availableWidth + ",0)");
2538 // Show error if series goes below 100%
2539 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2541 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2542 if (tempDisabled.length) {
2543 wrap.append('text').attr('class', 'tempDisabled')
2544 .attr('x', availableWidth / 2)
2545 .attr('y', '-.71em')
2546 .style('text-anchor', 'end')
2547 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2550 //Set up interactive layer
2551 if (useInteractiveGuideline) {
2553 .width(availableWidth)
2554 .height(availableHeight)
2555 .margin({left:margin.left,top:margin.top})
2556 .svgContainer(container)
2558 wrap.select(".nv-interactive").call(interactiveLayer);
2561 gEnter.select('.nv-background')
2564 g.select('.nv-background rect')
2565 .attr('width', availableWidth)
2566 .attr('height', availableHeight);
2569 //.x(function(d) { return d.x })
2570 .y(function(d) { return d.display.y })
2571 .width(availableWidth)
2572 .height(availableHeight)
2573 .color(data.map(function(d,i) {
2574 return d.color || color(d, i);
2575 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2577 var linesWrap = g.select('.nv-linesWrap')
2578 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
2580 linesWrap.call(lines);
2582 //Store a series index number in the data array.
2583 data.forEach(function(d,i) {
2587 var avgLineData = data.filter(function(d) {
2588 return !d.disabled && !!average(d);
2591 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2592 .data(avgLineData, function(d) { return d.key; });
2594 var getAvgLineY = function(d) {
2595 //If average lines go off the svg element, clamp them to the svg bounds.
2596 var yVal = y(average(d));
2597 if (yVal < 0) return 0;
2598 if (yVal > availableHeight) return availableHeight;
2604 .style('stroke-width',2)
2605 .style('stroke-dasharray','10,10')
2606 .style('stroke',function (d,i) {
2607 return lines.color()(d,d.seriesIndex);
2610 .attr('x2',availableWidth)
2611 .attr('y1', getAvgLineY)
2612 .attr('y2', getAvgLineY);
2615 .style('stroke-opacity',function(d){
2616 //If average lines go offscreen, make them transparent
2617 var yVal = y(average(d));
2618 if (yVal < 0 || yVal > availableHeight) return 0;
2622 .attr('x2',availableWidth)
2623 .attr('y1', getAvgLineY)
2624 .attr('y2', getAvgLineY);
2626 avgLines.exit().remove();
2629 var indexLine = linesWrap.selectAll('.nv-indexLine')
2631 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2634 .attr('fill', 'red')
2635 .attr('fill-opacity', .5)
2636 .style("pointer-events","all")
2640 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2641 .attr('height', availableHeight);
2647 .ticks( nv.utils.calcTicksX(availableWidth/70, data) )
2648 .tickSize(-availableHeight, 0);
2650 g.select('.nv-x.nv-axis')
2651 .attr('transform', 'translate(0,' + y.range()[0] + ')');
2652 g.select('.nv-x.nv-axis')
2659 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
2660 .tickSize( -availableWidth, 0);
2662 g.select('.nv-y.nv-axis')
2666 //============================================================
2667 // Event Handling/Dispatching (in chart's scope)
2668 //------------------------------------------------------------
2670 function updateZero() {
2674 //When dragging the index line, turn off line transitions.
2675 // Then turn them back on when done dragging.
2676 var oldDuration = chart.duration();
2679 chart.duration(oldDuration);
2682 g.select('.nv-background rect')
2683 .on('click', function() {
2684 index.x = d3.mouse(this)[0];
2685 index.i = Math.round(dx.invert(index.x));
2687 // update state and send stateChange with new index
2688 state.index = index.i;
2689 dispatch.stateChange(state);
2694 lines.dispatch.on('elementClick', function(e) {
2695 index.i = e.pointIndex;
2696 index.x = dx(index.i);
2698 // update state and send stateChange with new index
2699 state.index = index.i;
2700 dispatch.stateChange(state);
2705 controls.dispatch.on('legendClick', function(d,i) {
2706 d.disabled = !d.disabled;
2707 rescaleY = !d.disabled;
2709 state.rescaleY = rescaleY;
2710 dispatch.stateChange(state);
2714 legend.dispatch.on('stateChange', function(newState) {
2715 for (var key in newState)
2716 state[key] = newState[key];
2717 dispatch.stateChange(state);
2721 interactiveLayer.dispatch.on('elementMousemove', function(e) {
2722 lines.clearHighlights();
2723 var singlePoint, pointIndex, pointXLocation, allData = [];
2726 .filter(function(series, i) {
2727 series.seriesIndex = i;
2728 return !series.disabled;
2730 .forEach(function(series,i) {
2731 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2732 lines.highlightPoint(i, pointIndex, true);
2733 var point = series.values[pointIndex];
2734 if (typeof point === 'undefined') return;
2735 if (typeof singlePoint === 'undefined') singlePoint = point;
2736 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2739 value: chart.y()(point, pointIndex),
2740 color: color(series,series.seriesIndex)
2744 //Highlight the tooltip entry based on which point the mouse is closest to.
2745 if (allData.length > 2) {
2746 var yValue = chart.yScale().invert(e.mouseY);
2747 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
2748 var threshold = 0.03 * domainExtent;
2749 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
2750 if (indexToHighlight !== null)
2751 allData[indexToHighlight].highlight = true;
2754 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
2755 interactiveLayer.tooltip
2756 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2757 .chartContainer(that.parentNode)
2759 .valueFormatter(function(d,i) {
2760 return yAxis.tickFormat()(d);
2769 interactiveLayer.renderGuideLine(pointXLocation);
2772 interactiveLayer.dispatch.on("elementMouseout",function(e) {
2773 dispatch.tooltipHide();
2774 lines.clearHighlights();
2777 dispatch.on('tooltipShow', function(e) {
2778 if (tooltips) showTooltip(e, that.parentNode);
2781 // Update chart from a state object passed to event handler
2782 dispatch.on('changeState', function(e) {
2784 if (typeof e.disabled !== 'undefined') {
2785 data.forEach(function(series,i) {
2786 series.disabled = e.disabled[i];
2789 state.disabled = e.disabled;
2792 if (typeof e.index !== 'undefined') {
2794 index.x = dx(index.i);
2796 state.index = e.index;
2802 if (typeof e.rescaleY !== 'undefined') {
2803 rescaleY = e.rescaleY;
2811 renderWatch.renderEnd('cumulativeLineChart immediate');
2816 //============================================================
2817 // Event Handling/Dispatching (out of chart's scope)
2818 //------------------------------------------------------------
2820 lines.dispatch.on('elementMouseover.tooltip', function(e) {
2821 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
2822 dispatch.tooltipShow(e);
2825 lines.dispatch.on('elementMouseout.tooltip', function(e) {
2826 dispatch.tooltipHide(e);
2829 dispatch.on('tooltipHide', function() {
2830 if (tooltips) nv.tooltip.cleanup();
2833 //============================================================
2835 //------------------------------------------------------------
2837 var indexifyYGetter = null;
2838 /* Normalize the data according to an index point. */
2839 function indexify(idx, data) {
2840 if (!indexifyYGetter) indexifyYGetter = lines.y();
2841 return data.map(function(line, i) {
2845 var indexValue = line.values[idx];
2846 if (indexValue == null) {
2849 var v = indexifyYGetter(indexValue, idx);
2851 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
2852 if (v < -.95 && !noErrorCheck) {
2853 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
2855 line.tempDisabled = true;
2859 line.tempDisabled = false;
2861 line.values = line.values.map(function(point, pointIndex) {
2862 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
2870 //============================================================
2871 // Expose Public Variables
2872 //------------------------------------------------------------
2874 // expose chart's sub-components
2875 chart.dispatch = dispatch;
2876 chart.lines = lines;
2877 chart.legend = legend;
2878 chart.xAxis = xAxis;
2879 chart.yAxis = yAxis;
2880 chart.interactiveLayer = interactiveLayer;
2881 chart.state = state;
2883 chart.options = nv.utils.optionsFunc.bind(chart);
2885 chart._options = Object.create({}, {
2886 // simple options, just get/set the necessary values
2887 width: {get: function(){return width;}, set: function(_){width=_;}},
2888 height: {get: function(){return height;}, set: function(_){height=_;}},
2889 rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
2890 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
2891 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
2892 average: {get: function(){return average;}, set: function(_){average=_;}},
2893 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
2894 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
2895 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
2896 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
2897 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
2898 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
2899 noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
2901 // options that require extra logic in the setter
2902 margin: {get: function(){return margin;}, set: function(_){
2903 margin.top = _.top !== undefined ? _.top : margin.top;
2904 margin.right = _.right !== undefined ? _.right : margin.right;
2905 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2906 margin.left = _.left !== undefined ? _.left : margin.left;
2908 color: {get: function(){return color;}, set: function(_){
2909 color = nv.utils.getColor(_);
2910 legend.color(color);
2912 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
2913 useInteractiveGuideline = _;
2915 chart.interactive(false);
2916 chart.useVoronoi(false);
2919 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
2920 rightAlignYAxis = _;
2921 yAxis.orient( (_) ? 'right' : 'left');
2923 duration: {get: function(){return duration;}, set: function(_){
2925 lines.duration(duration);
2926 xAxis.duration(duration);
2927 yAxis.duration(duration);
2928 renderWatch.reset(duration);
2932 nv.utils.inheritOptions(chart, lines);
2933 nv.utils.initOptions(chart);
2936 };//TODO: consider deprecating by adding necessary features to multiBar model
2937 nv.models.discreteBar = function() {
2940 //============================================================
2941 // Public Variables with Default Settings
2942 //------------------------------------------------------------
2944 var margin = {top: 0, right: 0, bottom: 0, left: 0}
2947 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2948 , x = d3.scale.ordinal()
2949 , y = d3.scale.linear()
2950 , getX = function(d) { return d.x }
2951 , getY = function(d) { return d.y }
2952 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
2953 , color = nv.utils.defaultColor()
2954 , showValues = false
2955 , valueFormat = d3.format(',.2f')
2960 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
2961 , rectClass = 'discreteBar'
2965 //============================================================
2966 // Private Variables
2967 //------------------------------------------------------------
2970 var renderWatch = nv.utils.renderWatch(dispatch, duration);
2972 function chart(selection) {
2973 renderWatch.reset();
2974 selection.each(function(data) {
2975 var availableWidth = width - margin.left - margin.right,
2976 availableHeight = height - margin.top - margin.bottom,
2977 container = d3.select(this);
2978 nv.utils.initSVG(container);
2980 //add series index to each data point for reference
2981 data.forEach(function(series, i) {
2982 series.values.forEach(function(point) {
2988 // remap and flatten the data for use in calculating the scales' domains
2989 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
2990 data.map(function(d) {
2991 return d.values.map(function(d,i) {
2992 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
2996 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2997 .rangeBands(xRange || [0, availableWidth], .1);
2998 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
3000 // If showValues, pad the Y axis range to account for label height
3001 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3002 else y.range(yRange || [availableHeight, 0]);
3004 //store old scales if they exist
3006 y0 = y0 || y.copy().range([y(0),y(0)]);
3008 // Setup containers and skeleton of chart
3009 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3010 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3011 var gEnter = wrapEnter.append('g');
3012 var g = wrap.select('g');
3014 gEnter.append('g').attr('class', 'nv-groups');
3015 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3017 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3018 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3019 .data(function(d) { return d }, function(d) { return d.key });
3020 groups.enter().append('g')
3021 .style('stroke-opacity', 1e-6)
3022 .style('fill-opacity', 1e-6);
3024 .watchTransition(renderWatch, 'discreteBar: exit groups')
3025 .style('stroke-opacity', 1e-6)
3026 .style('fill-opacity', 1e-6)
3029 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3030 .classed('hover', function(d) { return d.hover });
3032 .watchTransition(renderWatch, 'discreteBar: groups')
3033 .style('stroke-opacity', 1)
3034 .style('fill-opacity', .75);
3036 var bars = groups.selectAll('g.nv-bar')
3037 .data(function(d) { return d.values });
3038 bars.exit().remove();
3040 var barsEnter = bars.enter().append('g')
3041 .attr('transform', function(d,i,j) {
3042 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3044 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3045 d3.select(this).classed('hover', true);
3046 dispatch.elementMouseover({
3049 series: data[d.series],
3050 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
3052 seriesIndex: d.series,
3056 .on('mouseout', function(d,i) {
3057 d3.select(this).classed('hover', false);
3058 dispatch.elementMouseout({
3061 series: data[d.series],
3063 seriesIndex: d.series,
3067 .on('click', function(d,i) {
3068 dispatch.elementClick({
3071 series: data[d.series],
3072 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
3074 seriesIndex: d.series,
3077 d3.event.stopPropagation();
3079 .on('dblclick', function(d,i) {
3080 dispatch.elementDblClick({
3083 series: data[d.series],
3084 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
3086 seriesIndex: d.series,
3089 d3.event.stopPropagation();
3092 barsEnter.append('rect')
3094 .attr('width', x.rangeBand() * .9 / data.length )
3097 barsEnter.append('text')
3098 .attr('text-anchor', 'middle')
3102 .text(function(d,i) { return valueFormat(getY(d,i)) })
3103 .watchTransition(renderWatch, 'discreteBar: bars text')
3104 .attr('x', x.rangeBand() * .9 / 2)
3105 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3109 bars.selectAll('text').remove();
3113 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3114 .style('fill', function(d,i) { return d.color || color(d,i) })
3115 .style('stroke', function(d,i) { return d.color || color(d,i) })
3117 .attr('class', rectClass)
3118 .watchTransition(renderWatch, 'discreteBar: bars rect')
3119 .attr('width', x.rangeBand() * .9 / data.length);
3120 bars.watchTransition(renderWatch, 'discreteBar: bars')
3121 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3122 .attr('transform', function(d,i) {
3123 var left = x(getX(d,i)) + x.rangeBand() * .05,
3124 top = getY(d,i) < 0 ?
3126 y(0) - y(getY(d,i)) < 1 ?
3127 y(0) - 1 : //make 1 px positive bars show up above y=0
3130 return 'translate(' + left + ', ' + top + ')'
3133 .attr('height', function(d,i) {
3134 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3138 //store old scales for use in transitions on update
3144 renderWatch.renderEnd('discreteBar immediate');
3148 //============================================================
3149 // Expose Public Variables
3150 //------------------------------------------------------------
3152 chart.dispatch = dispatch;
3153 chart.options = nv.utils.optionsFunc.bind(chart);
3155 chart._options = Object.create({}, {
3156 // simple options, just get/set the necessary values
3157 width: {get: function(){return width;}, set: function(_){width=_;}},
3158 height: {get: function(){return height;}, set: function(_){height=_;}},
3159 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
3160 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
3161 x: {get: function(){return getX;}, set: function(_){getX=_;}},
3162 y: {get: function(){return getY;}, set: function(_){getY=_;}},
3163 xScale: {get: function(){return x;}, set: function(_){x=_;}},
3164 yScale: {get: function(){return y;}, set: function(_){y=_;}},
3165 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
3166 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
3167 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
3168 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
3169 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
3170 id: {get: function(){return id;}, set: function(_){id=_;}},
3171 rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
3173 // options that require extra logic in the setter
3174 margin: {get: function(){return margin;}, set: function(_){
3175 margin.top = _.top !== undefined ? _.top : margin.top;
3176 margin.right = _.right !== undefined ? _.right : margin.right;
3177 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3178 margin.left = _.left !== undefined ? _.left : margin.left;
3180 color: {get: function(){return color;}, set: function(_){
3181 color = nv.utils.getColor(_);
3183 duration: {get: function(){return duration;}, set: function(_){
3185 renderWatch.reset(duration);
3189 nv.utils.initOptions(chart);
3194 nv.models.discreteBarChart = function() {
3197 //============================================================
3198 // Public Variables with Default Settings
3199 //------------------------------------------------------------
3201 var discretebar = nv.models.discreteBar()
3202 , xAxis = nv.models.axis()
3203 , yAxis = nv.models.axis()
3206 var margin = {top: 15, right: 10, bottom: 50, left: 60}
3209 , color = nv.utils.getColor()
3212 , rightAlignYAxis = false
3213 , staggerLabels = false
3215 , tooltip = function(key, x, y, e, graph) {
3216 return '<h3>' + x + '</h3>' +
3221 , noData = "No Data Available."
3222 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate','renderEnd')
3228 .highlightZero(false)
3230 .tickFormat(function(d) { return d })
3233 .orient((rightAlignYAxis) ? 'right' : 'left')
3234 .tickFormat(d3.format(',.1f'))
3237 //============================================================
3238 // Private Variables
3239 //------------------------------------------------------------
3241 var showTooltip = function(e, offsetElement) {
3242 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3243 top = e.pos[1] + ( offsetElement.offsetTop || 0),
3244 x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3245 y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3246 content = tooltip(e.series.key, x, y, e, chart);
3248 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3251 var renderWatch = nv.utils.renderWatch(dispatch, duration);
3253 function chart(selection) {
3254 renderWatch.reset();
3255 renderWatch.models(discretebar);
3256 if (showXAxis) renderWatch.models(xAxis);
3257 if (showYAxis) renderWatch.models(yAxis);
3259 selection.each(function(data) {
3260 var container = d3.select(this),
3262 nv.utils.initSVG(container);
3263 var availableWidth = (width || parseInt(container.style('width')) || 960)
3264 - margin.left - margin.right,
3265 availableHeight = (height || parseInt(container.style('height')) || 400)
3266 - margin.top - margin.bottom;
3268 chart.update = function() {
3269 dispatch.beforeUpdate();
3270 container.transition().duration(duration).call(chart);
3272 chart.container = this;
3274 // Display No Data message if there's nothing to show.
3275 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3276 var noDataText = container.selectAll('.nv-noData').data([noData]);
3278 noDataText.enter().append('text')
3279 .attr('class', 'nvd3 nv-noData')
3280 .attr('dy', '-.7em')
3281 .style('text-anchor', 'middle');
3284 .attr('x', margin.left + availableWidth / 2)
3285 .attr('y', margin.top + availableHeight / 2)
3286 .text(function(d) { return d });
3290 container.selectAll('.nv-noData').remove();
3294 x = discretebar.xScale();
3295 y = discretebar.yScale().clamp(true);
3297 // Setup containers and skeleton of chart
3298 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3299 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3300 var defsEnter = gEnter.append('defs');
3301 var g = wrap.select('g');
3303 gEnter.append('g').attr('class', 'nv-x nv-axis');
3304 gEnter.append('g').attr('class', 'nv-y nv-axis')
3305 .append('g').attr('class', 'nv-zeroLine')
3308 gEnter.append('g').attr('class', 'nv-barsWrap');
3310 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3312 if (rightAlignYAxis) {
3313 g.select(".nv-y.nv-axis")
3314 .attr("transform", "translate(" + availableWidth + ",0)");
3317 // Main Chart Component(s)
3319 .width(availableWidth)
3320 .height(availableHeight);
3322 var barsWrap = g.select('.nv-barsWrap')
3323 .datum(data.filter(function(d) { return !d.disabled }))
3325 barsWrap.transition().call(discretebar);
3328 defsEnter.append('clipPath')
3329 .attr('id', 'nv-x-label-clip-' + discretebar.id())
3332 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3333 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3335 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3341 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
3342 .tickSize(-availableHeight, 0);
3344 g.select('.nv-x.nv-axis')
3345 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3346 g.select('.nv-x.nv-axis').call(xAxis);
3348 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3349 if (staggerLabels) {
3352 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3359 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3360 .tickSize( -availableWidth, 0);
3362 g.select('.nv-y.nv-axis').call(yAxis);
3366 g.select(".nv-zeroLine line")
3368 .attr("x2",availableWidth)
3373 //============================================================
3374 // Event Handling/Dispatching (in chart's scope)
3375 //------------------------------------------------------------
3377 dispatch.on('tooltipShow', function(e) {
3378 if (tooltips) showTooltip(e, that.parentNode);
3383 renderWatch.renderEnd('discreteBar chart immediate');
3387 //============================================================
3388 // Event Handling/Dispatching (out of chart's scope)
3389 //------------------------------------------------------------
3391 discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3392 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3393 dispatch.tooltipShow(e);
3396 discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3397 dispatch.tooltipHide(e);
3400 dispatch.on('tooltipHide', function() {
3401 if (tooltips) nv.tooltip.cleanup();
3404 //============================================================
3405 // Expose Public Variables
3406 //------------------------------------------------------------
3408 chart.dispatch = dispatch;
3409 chart.discretebar = discretebar;
3410 chart.xAxis = xAxis;
3411 chart.yAxis = yAxis;
3413 chart.options = nv.utils.optionsFunc.bind(chart);
3415 chart._options = Object.create({}, {
3416 // simple options, just get/set the necessary values
3417 width: {get: function(){return width;}, set: function(_){width=_;}},
3418 height: {get: function(){return height;}, set: function(_){height=_;}},
3419 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
3420 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
3421 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
3422 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
3423 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
3424 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
3426 // options that require extra logic in the setter
3427 margin: {get: function(){return margin;}, set: function(_){
3428 margin.top = _.top !== undefined ? _.top : margin.top;
3429 margin.right = _.right !== undefined ? _.right : margin.right;
3430 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3431 margin.left = _.left !== undefined ? _.left : margin.left;
3433 duration: {get: function(){return duration;}, set: function(_){
3435 renderWatch.reset(duration);
3436 discretebar.duration(duration);
3437 xAxis.duration(duration);
3438 yAxis.duration(duration);
3440 color: {get: function(){return color;}, set: function(_){
3441 color = nv.utils.getColor(_);
3442 discretebar.color(color);
3444 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
3445 rightAlignYAxis = _;
3446 yAxis.orient( (_) ? 'right' : 'left');
3450 nv.utils.inheritOptions(chart, discretebar);
3451 nv.utils.initOptions(chart);
3456 nv.models.distribution = function() {
3458 //============================================================
3459 // Public Variables with Default Settings
3460 //------------------------------------------------------------
3462 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3463 , width = 400 //technically width or height depending on x or y....
3465 , axis = 'x' // 'x' or 'y'... horizontal or vertical
3466 , getData = function(d) { return d[axis] } // defaults d.x or d.y
3467 , color = nv.utils.defaultColor()
3468 , scale = d3.scale.linear()
3471 , dispatch = d3.dispatch('renderEnd')
3474 //============================================================
3477 //============================================================
3478 // Private Variables
3479 //------------------------------------------------------------
3482 var renderWatch = nv.utils.renderWatch(dispatch, duration);
3484 //============================================================
3487 function chart(selection) {
3488 renderWatch.reset();
3489 selection.each(function(data) {
3490 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
3491 naxis = axis == 'x' ? 'y' : 'x',
3492 container = d3.select(this);
3493 nv.utils.initSVG(container);
3495 //------------------------------------------------------------
3498 scale0 = scale0 || scale;
3500 //------------------------------------------------------------
3503 //------------------------------------------------------------
3504 // Setup containers and skeleton of chart
3506 var wrap = container.selectAll('g.nv-distribution').data([data]);
3507 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
3508 var gEnter = wrapEnter.append('g');
3509 var g = wrap.select('g');
3511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
3513 //------------------------------------------------------------
3516 var distWrap = g.selectAll('g.nv-dist')
3517 .data(function(d) { return d }, function(d) { return d.key });
3519 distWrap.enter().append('g');
3521 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
3522 .style('stroke', function(d,i) { return color(d, i) });
3524 var dist = distWrap.selectAll('line.nv-dist' + axis)
3525 .data(function(d) { return d.values })
3526 dist.enter().append('line')
3527 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
3528 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
3529 renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
3531 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3532 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3533 .style('stroke-opacity', 0)
3536 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
3537 .attr(naxis + '1', 0)
3538 .attr(naxis + '2', size);
3539 renderWatch.transition(dist, 'dist')
3541 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3542 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3545 scale0 = scale.copy();
3548 renderWatch.renderEnd('distribution immediate');
3553 //============================================================
3554 // Expose Public Variables
3555 //------------------------------------------------------------
3556 chart.options = nv.utils.optionsFunc.bind(chart);
3557 chart.dispatch = dispatch;
3559 chart.margin = function(_) {
3560 if (!arguments.length) return margin;
3561 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3562 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3563 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3564 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3568 chart.width = function(_) {
3569 if (!arguments.length) return width;
3574 chart.axis = function(_) {
3575 if (!arguments.length) return axis;
3580 chart.size = function(_) {
3581 if (!arguments.length) return size;
3586 chart.getData = function(_) {
3587 if (!arguments.length) return getData;
3588 getData = d3.functor(_);
3592 chart.scale = function(_) {
3593 if (!arguments.length) return scale;
3598 chart.color = function(_) {
3599 if (!arguments.length) return color;
3600 color = nv.utils.getColor(_);
3604 chart.duration = function(_) {
3605 if (!arguments.length) return duration;
3607 renderWatch.reset(duration);
3610 //============================================================
3615 //TODO: consider deprecating and using multibar with single series for this
3616 nv.models.historicalBar = function() {
3619 //============================================================
3620 // Public Variables with Default Settings
3621 //------------------------------------------------------------
3623 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3626 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3627 , x = d3.scale.linear()
3628 , y = d3.scale.linear()
3629 , getX = function(d) { return d.x }
3630 , getY = function(d) { return d.y }
3635 , color = nv.utils.defaultColor()
3640 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
3641 , interactive = true
3644 var renderWatch = nv.utils.renderWatch(dispatch, 0);
3646 function chart(selection) {
3647 selection.each(function(data) {
3648 renderWatch.reset();
3650 var container = d3.select(this);
3651 var availableWidth = (width || parseInt(container.style('width')) || 960)
3652 - margin.left - margin.right;
3653 var availableHeight = (height || parseInt(container.style('height')) || 400)
3654 - margin.top - margin.bottom;
3656 nv.utils.initSVG(container);
3659 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
3662 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
3664 x.range(xRange || [0, availableWidth]);
3666 y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
3667 .range(yRange || [availableHeight, 0]);
3669 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
3670 if (x.domain()[0] === x.domain()[1])
3672 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3675 if (y.domain()[0] === y.domain()[1])
3677 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3680 // Setup containers and skeleton of chart
3681 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
3682 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
3683 var defsEnter = wrapEnter.append('defs');
3684 var gEnter = wrapEnter.append('g');
3685 var g = wrap.select('g');
3687 gEnter.append('g').attr('class', 'nv-bars');
3688 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3691 .on('click', function(d,i) {
3692 dispatch.chartClick({
3700 defsEnter.append('clipPath')
3701 .attr('id', 'nv-chart-clip-path-' + id)
3704 wrap.select('#nv-chart-clip-path-' + id + ' rect')
3705 .attr('width', availableWidth)
3706 .attr('height', availableHeight);
3708 g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3710 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3711 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
3712 bars.exit().remove();
3714 var barsEnter = bars.enter().append('rect')
3716 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
3717 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
3718 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3719 .on('mouseover', function(d,i) {
3720 if (!interactive) return;
3721 d3.select(this).classed('hover', true);
3722 dispatch.elementMouseover({
3725 pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3732 .on('mouseout', function(d,i) {
3733 if (!interactive) return;
3734 d3.select(this).classed('hover', false);
3735 dispatch.elementMouseout({
3743 .on('click', function(d,i) {
3744 if (!interactive) return;
3745 dispatch.elementClick({
3750 pos: [x(getX(d,i)), y(getY(d,i))],
3754 d3.event.stopPropagation();
3756 .on('dblclick', function(d,i) {
3757 if (!interactive) return;
3758 dispatch.elementDblClick({
3763 pos: [x(getX(d,i)), y(getY(d,i))],
3767 d3.event.stopPropagation();
3771 .attr('fill', function(d,i) { return color(d, i); })
3772 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3773 .watchTransition(renderWatch, 'bars')
3774 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3775 //TODO: better width calculations that don't assume always uniform data spacing;w
3776 .attr('width', (availableWidth / data[0].values.length) * .9 );
3778 bars.watchTransition(renderWatch, 'bars')
3779 .attr('y', function(d,i) {
3780 var rval = getY(d,i) < 0 ?
3782 y(0) - y(getY(d,i)) < 1 ?
3785 return nv.utils.NaNtoZero(rval);
3787 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
3791 renderWatch.renderEnd('historicalBar immediate');
3795 //Create methods to allow outside functions to highlight a specific bar.
3796 chart.highlightPoint = function(pointIndex, isHoverOver) {
3797 d3.select(".nv-historicalBar-" + id)
3798 .select(".nv-bars .nv-bar-0-" + pointIndex)
3799 .classed("hover", isHoverOver)
3803 chart.clearHighlights = function() {
3804 d3.select(".nv-historicalBar-" + id)
3805 .select(".nv-bars .nv-bar.hover")
3806 .classed("hover", false)
3810 //============================================================
3811 // Expose Public Variables
3812 //------------------------------------------------------------
3814 chart.dispatch = dispatch;
3815 chart.options = nv.utils.optionsFunc.bind(chart);
3817 chart._options = Object.create({}, {
3818 // simple options, just get/set the necessary values
3819 width: {get: function(){return width;}, set: function(_){width=_;}},
3820 height: {get: function(){return height;}, set: function(_){height=_;}},
3821 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
3822 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
3823 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
3824 x: {get: function(){return getX;}, set: function(_){getX=_;}},
3825 y: {get: function(){return getY;}, set: function(_){getY=_;}},
3826 xScale: {get: function(){return x;}, set: function(_){x=_;}},
3827 yScale: {get: function(){return y;}, set: function(_){y=_;}},
3828 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
3829 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
3830 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
3831 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
3832 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
3833 id: {get: function(){return id;}, set: function(_){id=_;}},
3834 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
3836 // options that require extra logic in the setter
3837 margin: {get: function(){return margin;}, set: function(_){
3838 margin.top = _.top !== undefined ? _.top : margin.top;
3839 margin.right = _.right !== undefined ? _.right : margin.right;
3840 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3841 margin.left = _.left !== undefined ? _.left : margin.left;
3843 color: {get: function(){return color;}, set: function(_){
3844 color = nv.utils.getColor(_);
3848 nv.utils.initOptions(chart);
3853 nv.models.historicalBarChart = function(bar_model) {
3856 //============================================================
3857 // Public Variables with Default Settings
3858 //------------------------------------------------------------
3860 var bars = bar_model || nv.models.historicalBar()
3861 , xAxis = nv.models.axis()
3862 , yAxis = nv.models.axis()
3863 , legend = nv.models.legend()
3864 , interactiveLayer = nv.interactiveGuideline()
3868 var margin = {top: 30, right: 90, bottom: 50, left: 90}
3869 , color = nv.utils.defaultColor()
3872 , showLegend = false
3875 , rightAlignYAxis = false
3876 , useInteractiveGuideline = false
3878 , tooltip = function(key, x, y, e, graph) {
3879 return '<h3>' + key + '</h3>' +
3880 '<p>' + y + ' at ' + x + '</p>'
3885 , defaultState = null
3886 , noData = 'No Data Available.'
3887 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
3888 , transitionDuration = 250
3896 .orient( (rightAlignYAxis) ? 'right' : 'left')
3899 //============================================================
3900 // Private Variables
3901 //------------------------------------------------------------
3903 var showTooltip = function(e, offsetElement) {
3905 // New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
3906 if (offsetElement) {
3907 var svg = d3.select(offsetElement).select('svg');
3908 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
3910 viewBox = viewBox.split(' ');
3911 var ratio = parseInt(svg.style('width')) / viewBox[2];
3912 e.pos[0] = e.pos[0] * ratio;
3913 e.pos[1] = e.pos[1] * ratio;
3917 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3918 top = e.pos[1] + ( offsetElement.offsetTop || 0),
3919 x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
3920 y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
3921 content = tooltip(e.series.key, x, y, e, chart);
3923 nv.tooltip.show([left, top], content, null, null, offsetElement);
3925 var renderWatch = nv.utils.renderWatch(dispatch, 0);
3927 function chart(selection) {
3928 selection.each(function(data) {
3929 renderWatch.reset();
3930 renderWatch.models(bars);
3931 if (showXAxis) renderWatch.models(xAxis);
3932 if (showYAxis) renderWatch.models(yAxis);
3934 var container = d3.select(this),
3936 nv.utils.initSVG(container);
3937 var availableWidth = (width || parseInt(container.style('width')) || 960)
3938 - margin.left - margin.right,
3939 availableHeight = (height || parseInt(container.style('height')) || 400)
3940 - margin.top - margin.bottom;
3943 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
3944 chart.container = this;
3946 //set state.disabled
3947 state.disabled = data.map(function(d) { return !!d.disabled });
3949 if (!defaultState) {
3952 for (key in state) {
3953 if (state[key] instanceof Array)
3954 defaultState[key] = state[key].slice(0);
3956 defaultState[key] = state[key];
3960 // Display noData message if there's nothing to show.
3961 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3962 var noDataText = container.selectAll('.nv-noData').data([noData]);
3964 noDataText.enter().append('text')
3965 .attr('class', 'nvd3 nv-noData')
3966 .attr('dy', '-.7em')
3967 .style('text-anchor', 'middle');
3970 .attr('x', margin.left + availableWidth / 2)
3971 .attr('y', margin.top + availableHeight / 2)
3972 .text(function(d) { return d });
3976 container.selectAll('.nv-noData').remove();
3983 // Setup containers and skeleton of chart
3984 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
3985 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
3986 var g = wrap.select('g');
3988 gEnter.append('g').attr('class', 'nv-x nv-axis');
3989 gEnter.append('g').attr('class', 'nv-y nv-axis');
3990 gEnter.append('g').attr('class', 'nv-barsWrap');
3991 gEnter.append('g').attr('class', 'nv-legendWrap');
3992 gEnter.append('g').attr('class', 'nv-interactive');
3996 legend.width(availableWidth);
3998 g.select('.nv-legendWrap')
4002 if ( margin.top != legend.height()) {
4003 margin.top = legend.height();
4004 availableHeight = (height || parseInt(container.style('height')) || 400)
4005 - margin.top - margin.bottom;
4008 wrap.select('.nv-legendWrap')
4009 .attr('transform', 'translate(0,' + (-margin.top) +')')
4011 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4013 if (rightAlignYAxis) {
4014 g.select(".nv-y.nv-axis")
4015 .attr("transform", "translate(" + availableWidth + ",0)");
4018 //Set up interactive layer
4019 if (useInteractiveGuideline) {
4021 .width(availableWidth)
4022 .height(availableHeight)
4023 .margin({left:margin.left, top:margin.top})
4024 .svgContainer(container)
4026 wrap.select(".nv-interactive").call(interactiveLayer);
4029 .width(availableWidth)
4030 .height(availableHeight)
4031 .color(data.map(function(d,i) {
4032 return d.color || color(d, i);
4033 }).filter(function(d,i) { return !data[i].disabled }));
4035 var barsWrap = g.select('.nv-barsWrap')
4036 .datum(data.filter(function(d) { return !d.disabled }));
4037 barsWrap.transition().call(bars);
4043 .tickSize(-availableHeight, 0);
4045 g.select('.nv-x.nv-axis')
4046 .attr('transform', 'translate(0,' + y.range()[0] + ')');
4047 g.select('.nv-x.nv-axis')
4055 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4056 .tickSize( -availableWidth, 0);
4058 g.select('.nv-y.nv-axis')
4063 //============================================================
4064 // Event Handling/Dispatching (in chart's scope)
4065 //------------------------------------------------------------
4067 interactiveLayer.dispatch.on('elementMousemove', function(e) {
4068 bars.clearHighlights();
4070 var singlePoint, pointIndex, pointXLocation, allData = [];
4072 .filter(function(series, i) {
4073 series.seriesIndex = i;
4074 return !series.disabled;
4076 .forEach(function(series,i) {
4077 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
4078 bars.highlightPoint(pointIndex,true);
4079 var point = series.values[pointIndex];
4080 if (typeof point === 'undefined') return;
4081 if (typeof singlePoint === 'undefined') singlePoint = point;
4082 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
4085 value: chart.y()(point, pointIndex),
4086 color: color(series,series.seriesIndex),
4087 data: series.values[pointIndex]
4091 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
4092 interactiveLayer.tooltip
4093 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
4094 .chartContainer(that.parentNode)
4096 .valueFormatter(function(d,i) {
4097 return yAxis.tickFormat()(d);
4106 interactiveLayer.renderGuideLine(pointXLocation);
4110 interactiveLayer.dispatch.on("elementMouseout",function(e) {
4111 dispatch.tooltipHide();
4112 bars.clearHighlights();
4115 legend.dispatch.on('legendClick', function(d,i) {
4116 d.disabled = !d.disabled;
4118 if (!data.filter(function(d) { return !d.disabled }).length) {
4119 data.map(function(d) {
4121 wrap.selectAll('.nv-series').classed('disabled', false);
4126 state.disabled = data.map(function(d) { return !!d.disabled });
4127 dispatch.stateChange(state);
4129 selection.transition().call(chart);
4132 legend.dispatch.on('legendDblclick', function(d) {
4133 //Double clicking should always enable current series, and disabled all others.
4134 data.forEach(function(d) {
4139 state.disabled = data.map(function(d) { return !!d.disabled });
4140 dispatch.stateChange(state);
4144 dispatch.on('tooltipShow', function(e) {
4145 if (tooltips) showTooltip(e, that.parentNode);
4148 dispatch.on('changeState', function(e) {
4150 if (typeof e.disabled !== 'undefined') {
4151 data.forEach(function(series,i) {
4152 series.disabled = e.disabled[i];
4155 state.disabled = e.disabled;
4162 renderWatch.renderEnd('historicalBarChart immediate');
4166 //============================================================
4167 // Event Handling/Dispatching (out of chart's scope)
4168 //------------------------------------------------------------
4170 bars.dispatch.on('elementMouseover.tooltip', function(e) {
4171 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
4172 dispatch.tooltipShow(e);
4175 bars.dispatch.on('elementMouseout.tooltip', function(e) {
4176 dispatch.tooltipHide(e);
4179 dispatch.on('tooltipHide', function() {
4180 if (tooltips) nv.tooltip.cleanup();
4183 //============================================================
4184 // Expose Public Variables
4185 //------------------------------------------------------------
4187 // expose chart's sub-components
4188 chart.dispatch = dispatch;
4190 chart.legend = legend;
4191 chart.xAxis = xAxis;
4192 chart.yAxis = yAxis;
4193 chart.interactiveLayer = interactiveLayer;
4195 chart.options = nv.utils.optionsFunc.bind(chart);
4197 chart._options = Object.create({}, {
4198 // simple options, just get/set the necessary values
4199 width: {get: function(){return width;}, set: function(_){width=_;}},
4200 height: {get: function(){return height;}, set: function(_){height=_;}},
4201 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
4202 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
4203 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
4204 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
4205 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
4206 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
4207 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
4209 // options that require extra logic in the setter
4210 margin: {get: function(){return margin;}, set: function(_){
4211 margin.top = _.top !== undefined ? _.top : margin.top;
4212 margin.right = _.right !== undefined ? _.right : margin.right;
4213 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4214 margin.left = _.left !== undefined ? _.left : margin.left;
4216 color: {get: function(){return color;}, set: function(_){
4217 color = nv.utils.getColor(_);
4218 legend.color(color);
4221 duration: {get: function(){return transitionDuration;}, set: function(_){
4222 transitionDuration=_;
4223 renderWatch.reset(transitionDuration);
4224 yAxis.duration(transitionDuration);
4225 xAxis.duration(transitionDuration);
4227 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
4228 rightAlignYAxis = _;
4229 yAxis.orient( (_) ? 'right' : 'left');
4231 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
4232 useInteractiveGuideline = _;
4234 chart.interactive(false);
4239 nv.utils.inheritOptions(chart, bars);
4240 nv.utils.initOptions(chart);
4246 // ohlcChart is just a historical chart with oclc bars and some tweaks
4247 nv.models.ohlcBarChart = function() {
4248 var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
4250 // special default tooltip since we show multiple values per x
4251 chart.useInteractiveGuideline(true);
4252 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
4253 // we assume only one series exists for this chart
4254 var d = data.series[0].data;
4255 // match line colors as defined in nv.d3.css
4256 var color = d.open < d.close ? "2ca02c" : "d62728";
4258 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
4260 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
4261 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
4262 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
4263 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
4267 };nv.models.legend = function() {
4270 //============================================================
4271 // Public Variables with Default Settings
4272 //------------------------------------------------------------
4274 var margin = {top: 5, right: 0, bottom: 5, left: 0}
4277 , getKey = function(d) { return d.key }
4278 , color = nv.utils.defaultColor()
4281 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4282 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4283 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4286 function chart(selection) {
4287 selection.each(function(data) {
4288 var availableWidth = width - margin.left - margin.right,
4289 container = d3.select(this);
4290 nv.utils.initSVG(container);
4292 // Setup containers and skeleton of chart
4293 var wrap = container.selectAll('g.nv-legend').data([data]);
4294 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4295 var g = wrap.select('g');
4297 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4299 var series = g.selectAll('.nv-series')
4300 .data(function(d) { return d });
4301 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4302 .on('mouseover', function(d,i) {
4303 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
4305 .on('mouseout', function(d,i) {
4306 dispatch.legendMouseout(d,i);
4308 .on('click', function(d,i) {
4309 dispatch.legendClick(d,i);
4311 if (radioButtonMode) {
4312 //Radio button mode: set every series to disabled,
4313 // and enable the clicked series.
4314 data.forEach(function(series) { series.disabled = true});
4318 d.disabled = !d.disabled;
4319 if (data.every(function(series) { return series.disabled})) {
4320 //the default behavior of NVD3 legends is, if every single series
4321 // is disabled, turn all series' back on.
4322 data.forEach(function(series) { series.disabled = false});
4325 dispatch.stateChange({
4326 disabled: data.map(function(d) { return !!d.disabled })
4330 .on('dblclick', function(d,i) {
4331 dispatch.legendDblclick(d,i);
4333 //the default behavior of NVD3 legends, when double clicking one,
4334 // is to set all other series' to false, and make the double clicked series enabled.
4335 data.forEach(function(series) {
4336 series.disabled = true;
4339 dispatch.stateChange({
4340 disabled: data.map(function(d) { return !!d.disabled })
4344 seriesEnter.append('circle')
4345 .style('stroke-width', 2)
4346 .attr('class','nv-legend-symbol')
4348 seriesEnter.append('text')
4349 .attr('text-anchor', 'start')
4350 .attr('class','nv-legend-text')
4351 .attr('dy', '.32em')
4353 series.classed('nv-disabled', function(d) { return d.disabled });
4354 series.exit().remove();
4355 series.select('circle')
4356 .style('fill', function(d,i) { return d.color || color(d,i)})
4357 .style('stroke', function(d,i) { return d.color || color(d, i) });
4358 series.select('text').text(getKey);
4360 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4361 // NEW ALIGNING CODE, TODO: clean up
4364 var seriesWidths = [];
4365 series.each(function(d,i) {
4366 var legendText = d3.select(this).select('text');
4369 nodeTextLength = legendText.node().getComputedTextLength();
4370 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
4371 if(nodeTextLength <= 0) throw Error();
4374 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4377 seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
4380 var seriesPerRow = 0;
4381 var legendWidth = 0;
4382 var columnWidths = [];
4384 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4385 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4386 legendWidth += seriesWidths[seriesPerRow++];
4388 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4390 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4394 for (var k = 0; k < seriesWidths.length; k++) {
4395 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4396 columnWidths[k % seriesPerRow] = seriesWidths[k];
4399 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4404 var xPositions = [];
4405 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4406 xPositions[i] = curX;
4407 curX += columnWidths[i];
4411 .attr('transform', function(d, i) {
4412 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4415 //position legend as far right as possible within the total width
4417 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4420 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4423 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4432 .attr('transform', function(d, i) {
4433 var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4436 if (width < margin.left + margin.right + xpos + length) {
4442 if (newxpos > maxwidth) maxwidth = newxpos;
4444 return 'translate(' + xpos + ',' + ypos + ')';
4447 //position legend as far right as possible within the total width
4448 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4450 height = margin.top + margin.bottom + ypos + 15;
4457 //============================================================
4458 // Expose Public Variables
4459 //------------------------------------------------------------
4461 chart.dispatch = dispatch;
4462 chart.options = nv.utils.optionsFunc.bind(chart);
4464 chart._options = Object.create({}, {
4465 // simple options, just get/set the necessary values
4466 width: {get: function(){return width;}, set: function(_){width=_;}},
4467 height: {get: function(){return height;}, set: function(_){height=_;}},
4468 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
4469 align: {get: function(){return align;}, set: function(_){align=_;}},
4470 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
4471 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
4472 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
4474 // options that require extra logic in the setter
4475 margin: {get: function(){return margin;}, set: function(_){
4476 margin.top = _.top !== undefined ? _.top : margin.top;
4477 margin.right = _.right !== undefined ? _.right : margin.right;
4478 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4479 margin.left = _.left !== undefined ? _.left : margin.left;
4481 color: {get: function(){return color;}, set: function(_){
4482 color = nv.utils.getColor(_);
4486 nv.utils.initOptions(chart);
4491 nv.models.line = function() {
4493 //============================================================
4494 // Public Variables with Default Settings
4495 //------------------------------------------------------------
4497 var scatter = nv.models.scatter()
4500 var margin = {top: 0, right: 0, bottom: 0, left: 0}
4503 , color = nv.utils.defaultColor() // a function that returns a color
4504 , getX = function(d) { return d.x } // accessor to get the x value from a data point
4505 , getY = function(d) { return d.y } // accessor to get the y value from a data point
4506 , 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
4507 , isArea = function(d) { return d.area } // decides if a line is an area or just a line
4508 , clipEdge = false // if true, masks lines within x and y scale
4509 , x //can be accessed via chart.xScale()
4510 , y //can be accessed via chart.yScale()
4511 , interpolate = "linear" // controls the line interpolation
4513 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
4517 .pointSize(16) // default size
4518 .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
4521 //============================================================
4524 //============================================================
4525 // Private Variables
4526 //------------------------------------------------------------
4528 var x0, y0 //used to store previous scales
4529 , renderWatch = nv.utils.renderWatch(dispatch, duration)
4532 //============================================================
4535 function chart(selection) {
4536 renderWatch.reset();
4537 renderWatch.models(scatter);
4538 selection.each(function(data) {
4539 var availableWidth = width - margin.left - margin.right,
4540 availableHeight = height - margin.top - margin.bottom,
4541 container = d3.select(this);
4542 nv.utils.initSVG(container);
4545 x = scatter.xScale();
4546 y = scatter.yScale();
4551 // Setup containers and skeleton of chart
4552 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
4553 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
4554 var defsEnter = wrapEnter.append('defs');
4555 var gEnter = wrapEnter.append('g');
4556 var g = wrap.select('g');
4558 gEnter.append('g').attr('class', 'nv-groups');
4559 gEnter.append('g').attr('class', 'nv-scatterWrap');
4561 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4564 .width(availableWidth)
4565 .height(availableHeight);
4567 var scatterWrap = wrap.select('.nv-scatterWrap');
4568 scatterWrap.call(scatter);
4570 defsEnter.append('clipPath')
4571 .attr('id', 'nv-edge-clip-' + scatter.id())
4574 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
4575 .attr('width', availableWidth)
4576 .attr('height', (availableHeight > 0) ? availableHeight : 0);
4578 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4580 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
4582 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
4583 .data(function(d) { return d }, function(d) { return d.key });
4584 groups.enter().append('g')
4585 .style('stroke-opacity', 1e-6)
4586 .style('fill-opacity', 1e-6);
4588 groups.exit().remove();
4591 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
4592 .classed('hover', function(d) { return d.hover })
4593 .style('fill', function(d,i){ return color(d, i) })
4594 .style('stroke', function(d,i){ return color(d, i)});
4595 groups.watchTransition(renderWatch, 'line: groups')
4596 .style('stroke-opacity', 1)
4597 .style('fill-opacity', .5);
4599 var areaPaths = groups.selectAll('path.nv-area')
4600 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
4601 areaPaths.enter().append('path')
4602 .attr('class', 'nv-area')
4603 .attr('d', function(d) {
4604 return d3.svg.area()
4605 .interpolate(interpolate)
4607 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
4608 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
4609 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4610 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4611 .apply(this, [d.values])
4613 groups.exit().selectAll('path.nv-area')
4616 areaPaths.watchTransition(renderWatch, 'line: areaPaths')
4617 .attr('d', function(d) {
4618 return d3.svg.area()
4619 .interpolate(interpolate)
4621 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
4622 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
4623 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
4624 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
4625 .apply(this, [d.values])
4628 var linePaths = groups.selectAll('path.nv-line')
4629 .data(function(d) { return [d.values] });
4630 linePaths.enter().append('path')
4631 .attr('class', 'nv-line')
4634 .interpolate(interpolate)
4636 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
4637 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
4640 linePaths.watchTransition(renderWatch, 'line: linePaths')
4643 .interpolate(interpolate)
4645 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
4646 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
4649 //store old scales for use in transitions on update
4653 renderWatch.renderEnd('line immediate');
4658 //============================================================
4659 // Expose Public Variables
4660 //------------------------------------------------------------
4662 chart.dispatch = dispatch;
4663 chart.scatter = scatter;
4664 // Pass through events
4665 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); })
4666 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); })
4667 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); })
4669 chart.options = nv.utils.optionsFunc.bind(chart);
4671 chart._options = Object.create({}, {
4672 // simple options, just get/set the necessary values
4673 width: {get: function(){return width;}, set: function(_){width=_;}},
4674 height: {get: function(){return height;}, set: function(_){height=_;}},
4675 defined: {get: function(){return defined;}, set: function(_){defined=_;}},
4676 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
4677 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
4679 // options that require extra logic in the setter
4680 margin: {get: function(){return margin;}, set: function(_){
4681 margin.top = _.top !== undefined ? _.top : margin.top;
4682 margin.right = _.right !== undefined ? _.right : margin.right;
4683 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4684 margin.left = _.left !== undefined ? _.left : margin.left;
4686 duration: {get: function(){return duration;}, set: function(_){
4688 renderWatch.reset(duration);
4689 scatter.duration(duration);
4691 isArea: {get: function(){return isArea;}, set: function(_){
4692 isArea = d3.functor(_);
4694 x: {get: function(){return getX;}, set: function(_){
4698 y: {get: function(){return getY;}, set: function(_){
4702 color: {get: function(){return color;}, set: function(_){
4703 color = nv.utils.getColor(_);
4704 scatter.color(color);
4708 nv.utils.inheritOptions(chart, scatter);
4709 nv.utils.initOptions(chart);
4713 nv.models.lineChart = function() {
4716 //============================================================
4717 // Public Variables with Default Settings
4718 //------------------------------------------------------------
4720 var lines = nv.models.line()
4721 , xAxis = nv.models.axis()
4722 , yAxis = nv.models.axis()
4723 , legend = nv.models.legend()
4724 , interactiveLayer = nv.interactiveGuideline()
4727 var margin = {top: 30, right: 20, bottom: 50, left: 60}
4728 , color = nv.utils.defaultColor()
4734 , rightAlignYAxis = false
4735 , useInteractiveGuideline = false
4737 , tooltip = function(key, x, y, e, graph) {
4738 return '<h3>' + key + '</h3>' +
4739 '<p>' + y + ' at ' + x + '</p>'
4743 , state = nv.utils.state()
4744 , defaultState = null
4745 , noData = 'No Data Available.'
4746 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
4755 .orient((rightAlignYAxis) ? 'right' : 'left')
4758 //============================================================
4759 // Private Variables
4760 //------------------------------------------------------------
4762 var showTooltip = function(e, offsetElement) {
4763 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4764 top = e.pos[1] + ( offsetElement.offsetTop || 0),
4765 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
4766 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
4767 content = tooltip(e.series.key, x, y, e, chart);
4769 nv.tooltip.show([left, top], content, null, null, offsetElement);
4772 var renderWatch = nv.utils.renderWatch(dispatch, duration);
4774 var stateGetter = function(data) {
4777 active: data.map(function(d) { return !d.disabled })
4782 var stateSetter = function(data) {
4783 return function(state) {
4784 if (state.active !== undefined)
4785 data.forEach(function(series,i) {
4786 series.disabled = !state.active[i];
4791 function chart(selection) {
4792 renderWatch.reset();
4793 renderWatch.models(lines);
4794 if (showXAxis) renderWatch.models(xAxis);
4795 if (showYAxis) renderWatch.models(yAxis);
4797 selection.each(function(data) {
4798 var container = d3.select(this),
4800 nv.utils.initSVG(container);
4801 var availableWidth = (width || parseInt(container.style('width')) || 960)
4802 - margin.left - margin.right,
4803 availableHeight = (height || parseInt(container.style('height')) || 400)
4804 - margin.top - margin.bottom;
4807 chart.update = function() {
4809 container.call(chart);
4811 container.transition().duration(duration).call(chart)
4813 chart.container = this;
4816 .setter(stateSetter(data), chart.update)
4817 .getter(stateGetter(data))
4820 // DEPRECATED set state.disableddisabled
4821 state.disabled = data.map(function(d) { return !!d.disabled });
4823 if (!defaultState) {
4826 for (key in state) {
4827 if (state[key] instanceof Array)
4828 defaultState[key] = state[key].slice(0);
4830 defaultState[key] = state[key];
4834 // Display noData message if there's nothing to show.
4835 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4836 var noDataText = container.selectAll('.nv-noData').data([noData]);
4838 noDataText.enter().append('text')
4839 .attr('class', 'nvd3 nv-noData')
4840 .attr('dy', '-.7em')
4841 .style('text-anchor', 'middle');
4844 .attr('x', margin.left + availableWidth / 2)
4845 .attr('y', margin.top + availableHeight / 2)
4846 .text(function(d) { return d });
4850 container.selectAll('.nv-noData').remove();
4858 // Setup containers and skeleton of chart
4859 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
4860 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
4861 var g = wrap.select('g');
4863 gEnter.append("rect").style("opacity",0);
4864 gEnter.append('g').attr('class', 'nv-x nv-axis');
4865 gEnter.append('g').attr('class', 'nv-y nv-axis');
4866 gEnter.append('g').attr('class', 'nv-linesWrap');
4867 gEnter.append('g').attr('class', 'nv-legendWrap');
4868 gEnter.append('g').attr('class', 'nv-interactive');
4871 .attr("width",availableWidth)
4872 .attr("height",(availableHeight > 0) ? availableHeight : 0);
4876 legend.width(availableWidth);
4878 g.select('.nv-legendWrap')
4882 if ( margin.top != legend.height()) {
4883 margin.top = legend.height();
4884 availableHeight = (height || parseInt(container.style('height')) || 400)
4885 - margin.top - margin.bottom;
4888 wrap.select('.nv-legendWrap')
4889 .attr('transform', 'translate(0,' + (-margin.top) +')')
4892 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4894 if (rightAlignYAxis) {
4895 g.select(".nv-y.nv-axis")
4896 .attr("transform", "translate(" + availableWidth + ",0)");
4899 //Set up interactive layer
4900 if (useInteractiveGuideline) {
4902 .width(availableWidth)
4903 .height(availableHeight)
4904 .margin({left:margin.left, top:margin.top})
4905 .svgContainer(container)
4907 wrap.select(".nv-interactive").call(interactiveLayer);
4911 .width(availableWidth)
4912 .height(availableHeight)
4913 .color(data.map(function(d,i) {
4914 return d.color || color(d, i);
4915 }).filter(function(d,i) { return !data[i].disabled }));
4918 var linesWrap = g.select('.nv-linesWrap')
4919 .datum(data.filter(function(d) { return !d.disabled }));
4921 linesWrap.call(lines);
4927 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
4928 .tickSize(-availableHeight, 0);
4930 g.select('.nv-x.nv-axis')
4931 .attr('transform', 'translate(0,' + y.range()[0] + ')');
4932 g.select('.nv-x.nv-axis')
4939 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4940 .tickSize( -availableWidth, 0);
4942 g.select('.nv-y.nv-axis')
4946 //============================================================
4947 // Event Handling/Dispatching (in chart's scope)
4948 //------------------------------------------------------------
4950 legend.dispatch.on('stateChange', function(newState) {
4951 for (var key in newState)
4952 state[key] = newState[key];
4953 dispatch.stateChange(state);
4957 interactiveLayer.dispatch.on('elementMousemove', function(e) {
4958 lines.clearHighlights();
4959 var singlePoint, pointIndex, pointXLocation, allData = [];
4961 .filter(function(series, i) {
4962 series.seriesIndex = i;
4963 return !series.disabled;
4965 .forEach(function(series,i) {
4966 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
4967 lines.highlightPoint(i, pointIndex, true);
4968 var point = series.values[pointIndex];
4969 if (typeof point === 'undefined') return;
4970 if (typeof singlePoint === 'undefined') singlePoint = point;
4971 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
4974 value: chart.y()(point, pointIndex),
4975 color: color(series,series.seriesIndex)
4978 //Highlight the tooltip entry based on which point the mouse is closest to.
4979 if (allData.length > 2) {
4980 var yValue = chart.yScale().invert(e.mouseY);
4981 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
4982 var threshold = 0.03 * domainExtent;
4983 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
4984 if (indexToHighlight !== null)
4985 allData[indexToHighlight].highlight = true;
4988 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
4989 interactiveLayer.tooltip
4990 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
4991 .chartContainer(that.parentNode)
4993 .valueFormatter(function(d,i) {
4994 return yAxis.tickFormat()(d);
5003 interactiveLayer.renderGuideLine(pointXLocation);
5007 interactiveLayer.dispatch.on('elementClick', function(e) {
5008 var pointXLocation, allData = [];
5010 data.filter(function(series, i) {
5011 series.seriesIndex = i;
5012 return !series.disabled;
5013 }).forEach(function(series) {
5014 var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5015 var point = series.values[pointIndex];
5016 if (typeof point === 'undefined') return;
5017 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5018 var yPos = chart.yScale()(chart.y()(point,pointIndex));
5021 pointIndex: pointIndex,
5022 pos: [pointXLocation, yPos],
5023 seriesIndex: series.seriesIndex,
5028 lines.dispatch.elementClick(allData);
5031 interactiveLayer.dispatch.on("elementMouseout",function(e) {
5032 dispatch.tooltipHide();
5033 lines.clearHighlights();
5036 dispatch.on('tooltipShow', function(e) {
5037 if (tooltips) showTooltip(e, that.parentNode);
5040 dispatch.on('changeState', function(e) {
5042 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
5043 data.forEach(function(series,i) {
5044 series.disabled = e.disabled[i];
5047 state.disabled = e.disabled;
5055 renderWatch.renderEnd('lineChart immediate');
5059 //============================================================
5060 // Event Handling/Dispatching (out of chart's scope)
5061 //------------------------------------------------------------
5063 lines.dispatch.on('elementMouseover.tooltip', function(e) {
5064 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5065 dispatch.tooltipShow(e);
5068 lines.dispatch.on('elementMouseout.tooltip', function(e) {
5069 dispatch.tooltipHide(e);
5072 dispatch.on('tooltipHide', function() {
5073 if (tooltips) nv.tooltip.cleanup();
5076 //============================================================
5077 // Expose Public Variables
5078 //------------------------------------------------------------
5080 // expose chart's sub-components
5081 chart.dispatch = dispatch;
5082 chart.lines = lines;
5083 chart.legend = legend;
5084 chart.xAxis = xAxis;
5085 chart.yAxis = yAxis;
5086 chart.interactiveLayer = interactiveLayer;
5088 chart.dispatch = dispatch;
5089 chart.options = nv.utils.optionsFunc.bind(chart);
5091 chart._options = Object.create({}, {
5092 // simple options, just get/set the necessary values
5093 width: {get: function(){return width;}, set: function(_){width=_;}},
5094 height: {get: function(){return height;}, set: function(_){height=_;}},
5095 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
5096 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
5097 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
5098 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
5099 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
5100 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
5101 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
5103 // options that require extra logic in the setter
5104 margin: {get: function(){return margin;}, set: function(_){
5105 margin.top = _.top !== undefined ? _.top : margin.top;
5106 margin.right = _.right !== undefined ? _.right : margin.right;
5107 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5108 margin.left = _.left !== undefined ? _.left : margin.left;
5110 duration: {get: function(){return duration;}, set: function(_){
5112 renderWatch.reset(duration);
5113 lines.duration(duration);
5114 xAxis.duration(duration);
5115 yAxis.duration(duration);
5117 color: {get: function(){return color;}, set: function(_){
5118 color = nv.utils.getColor(_);
5119 legend.color(color);
5122 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
5123 rightAlignYAxis = _;
5124 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
5126 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
5127 useInteractiveGuideline = _;
5128 if (useInteractiveGuideline) {
5129 lines.interactive(false);
5130 lines.useVoronoi(false);
5135 nv.utils.inheritOptions(chart, lines);
5136 nv.utils.initOptions(chart);
5140 nv.models.linePlusBarChart = function() {
5143 //============================================================
5144 // Public Variables with Default Settings
5145 //------------------------------------------------------------
5147 var lines = nv.models.line()
5148 , lines2 = nv.models.line()
5149 , bars = nv.models.historicalBar()
5150 , bars2 = nv.models.historicalBar()
5151 , xAxis = nv.models.axis()
5152 , x2Axis = nv.models.axis()
5153 , y1Axis = nv.models.axis()
5154 , y2Axis = nv.models.axis()
5155 , y3Axis = nv.models.axis()
5156 , y4Axis = nv.models.axis()
5157 , legend = nv.models.legend()
5158 , brush = d3.svg.brush()
5161 var margin = {top: 30, right: 30, bottom: 30, left: 60}
5162 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5165 , getX = function(d) { return d.x }
5166 , getY = function(d) { return d.y }
5167 , color = nv.utils.defaultColor()
5169 , focusEnable = true
5170 , focusShowAxisY = false
5171 , focusShowAxisX = true
5174 , brushExtent = null
5176 , tooltip = function(key, x, y, e, graph) {
5177 return '<h3>' + key + '</h3>' +
5178 '<p>' + y + ' at ' + x + '</p>';
5186 , noData = "No Data Available."
5187 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
5188 , transitionDuration = 0
5189 , state = nv.utils.state()
5190 , defaultState = null
5220 //============================================================
5221 // Private Variables
5222 //------------------------------------------------------------
5224 var showTooltip = function(e, offsetElement) {
5226 e.pointIndex += Math.ceil(extent[0]);
5228 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5229 top = e.pos[1] + ( offsetElement.offsetTop || 0),
5230 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5231 y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5232 content = tooltip(e.series.key, x, y, e, chart);
5234 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5237 var stateGetter = function(data) {
5240 active: data.map(function(d) { return !d.disabled })
5245 var stateSetter = function(data) {
5246 return function(state) {
5247 if (state.active !== undefined)
5248 data.forEach(function(series,i) {
5249 series.disabled = !state.active[i];
5254 function chart(selection) {
5255 selection.each(function(data) {
5256 var container = d3.select(this),
5258 nv.utils.initSVG(container);
5259 var availableWidth = (width || parseInt(container.style('width')) || 960)
5260 - margin.left - margin.right,
5261 availableHeight1 = (height || parseInt(container.style('height')) || 400)
5262 - margin.top - margin.bottom - (focusEnable ? focusHeight : 0) ,
5263 availableHeight2 = focusHeight - margin2.top - margin2.bottom;
5265 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
5266 chart.container = this;
5269 .setter(stateSetter(data), chart.update)
5270 .getter(stateGetter(data))
5273 // DEPRECATED set state.disableddisabled
5274 state.disabled = data.map(function(d) { return !!d.disabled });
5276 if (!defaultState) {
5279 for (key in state) {
5280 if (state[key] instanceof Array)
5281 defaultState[key] = state[key].slice(0);
5283 defaultState[key] = state[key];
5287 // Display No Data message if there's nothing to show.
5288 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5289 var noDataText = container.selectAll('.nv-noData').data([noData]);
5291 noDataText.enter().append('text')
5292 .attr('class', 'nvd3 nv-noData')
5293 .attr('dy', '-.7em')
5294 .style('text-anchor', 'middle');
5297 .attr('x', margin.left + availableWidth / 2)
5298 .attr('y', margin.top + availableHeight1 / 2)
5299 .text(function(d) { return d });
5303 container.selectAll('.nv-noData').remove();
5307 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5308 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5311 x2 = x2Axis.scale();
5313 y2 = lines.yScale();
5314 y3 = bars2.yScale();
5315 y4 = lines2.yScale();
5318 .filter(function(d) { return !d.disabled && d.bar })
5320 return d.values.map(function(d,i) {
5321 return { x: getX(d,i), y: getY(d,i) }
5326 .filter(function(d) { return !d.disabled && !d.bar })
5328 return d.values.map(function(d,i) {
5329 return { x: getX(d,i), y: getY(d,i) }
5333 x.range([0, availableWidth]);
5335 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
5336 .range([0, availableWidth]);
5338 // Setup containers and skeleton of chart
5339 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
5340 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
5341 var g = wrap.select('g');
5343 gEnter.append('g').attr('class', 'nv-legendWrap');
5345 // this is the main chart
5346 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
5347 focusEnter.append('g').attr('class', 'nv-x nv-axis');
5348 focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
5349 focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
5350 focusEnter.append('g').attr('class', 'nv-barsWrap');
5351 focusEnter.append('g').attr('class', 'nv-linesWrap');
5353 // context chart is where you can focus in
5354 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
5355 contextEnter.append('g').attr('class', 'nv-x nv-axis');
5356 contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
5357 contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
5358 contextEnter.append('g').attr('class', 'nv-barsWrap');
5359 contextEnter.append('g').attr('class', 'nv-linesWrap');
5360 contextEnter.append('g').attr('class', 'nv-brushBackground');
5361 contextEnter.append('g').attr('class', 'nv-x nv-brush');
5363 //============================================================
5365 //------------------------------------------------------------
5368 legend.width( availableWidth / 2 );
5370 g.select('.nv-legendWrap')
5371 .datum(data.map(function(series) {
5372 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
5373 series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
5378 if ( margin.top != legend.height()) {
5379 margin.top = legend.height();
5380 availableHeight1 = (height || parseInt(container.style('height')) || 400)
5381 - margin.top - margin.bottom - focusHeight;
5384 g.select('.nv-legendWrap')
5385 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5388 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5390 //============================================================
5391 // Context chart (focus chart) components
5392 //------------------------------------------------------------
5394 // hide or show the focus context chart
5395 g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
5398 .width(availableWidth)
5399 .height(availableHeight2)
5400 .color(data.map(function (d, i) {
5401 return d.color || color(d, i);
5402 }).filter(function (d, i) {
5403 return !data[i].disabled && data[i].bar
5406 .width(availableWidth)
5407 .height(availableHeight2)
5408 .color(data.map(function (d, i) {
5409 return d.color || color(d, i);
5410 }).filter(function (d, i) {
5411 return !data[i].disabled && !data[i].bar
5414 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
5415 .datum(dataBars.length ? dataBars : [
5418 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
5419 .datum(!dataLines[0].disabled ? dataLines : [
5423 g.select('.nv-context')
5424 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
5426 bars2Wrap.transition().call(bars2);
5427 lines2Wrap.transition().call(lines2);
5429 // context (focus chart) axis controls
5430 if (focusShowAxisX) {
5432 .ticks(nv.utils.calcTicksX(availableWidth / 100, data))
5433 .tickSize(-availableHeight2, 0);
5434 g.select('.nv-context .nv-x.nv-axis')
5435 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
5436 g.select('.nv-context .nv-x.nv-axis').transition()
5440 if (focusShowAxisY) {
5443 .ticks( availableHeight2 / 36 )
5444 .tickSize( -availableWidth, 0);
5447 .ticks( availableHeight2 / 36 )
5448 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
5450 g.select('.nv-context .nv-y3.nv-axis')
5451 .style('opacity', dataBars.length ? 1 : 0)
5452 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
5453 g.select('.nv-context .nv-y2.nv-axis')
5454 .style('opacity', dataLines.length ? 1 : 0)
5455 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
5457 g.select('.nv-context .nv-y1.nv-axis').transition()
5459 g.select('.nv-context .nv-y2.nv-axis').transition()
5464 brush.x(x2).on('brush', onBrush);
5466 if (brushExtent) brush.extent(brushExtent);
5468 var brushBG = g.select('.nv-brushBackground').selectAll('g')
5469 .data([brushExtent || brush.extent()]);
5471 var brushBGenter = brushBG.enter()
5474 brushBGenter.append('rect')
5475 .attr('class', 'left')
5478 .attr('height', availableHeight2);
5480 brushBGenter.append('rect')
5481 .attr('class', 'right')
5484 .attr('height', availableHeight2);
5486 var gBrush = g.select('.nv-x.nv-brush')
5488 gBrush.selectAll('rect')
5490 .attr('height', availableHeight2);
5491 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
5493 //============================================================
5494 // Event Handling/Dispatching (in chart's scope)
5495 //------------------------------------------------------------
5497 legend.dispatch.on('stateChange', function(newState) {
5498 for (var key in newState)
5499 state[key] = newState[key];
5500 dispatch.stateChange(state);
5504 dispatch.on('tooltipShow', function(e) {
5505 if (tooltips) showTooltip(e, that.parentNode);
5508 // Update chart from a state object passed to event handler
5509 dispatch.on('changeState', function(e) {
5510 if (typeof e.disabled !== 'undefined') {
5511 data.forEach(function(series,i) {
5512 series.disabled = e.disabled[i];
5514 state.disabled = e.disabled;
5519 //============================================================
5521 //------------------------------------------------------------
5523 // Taken from crossfilter (http://square.github.com/crossfilter/)
5524 function resizePath(d) {
5525 var e = +(d == 'e'),
5527 y = availableHeight2 / 3;
5528 return 'M' + (.5 * x) + ',' + y
5529 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
5531 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
5533 + 'M' + (2.5 * x) + ',' + (y + 8)
5535 + 'M' + (4.5 * x) + ',' + (y + 8)
5536 + 'V' + (2 * y - 8);
5540 function updateBrushBG() {
5541 if (!brush.empty()) brush.extent(brushExtent);
5543 .data([brush.empty() ? x2.domain() : brushExtent])
5544 .each(function(d,i) {
5545 var leftWidth = x2(d[0]) - x2.range()[0],
5546 rightWidth = x2.range()[1] - x2(d[1]);
5547 d3.select(this).select('.left')
5548 .attr('width', leftWidth < 0 ? 0 : leftWidth);
5550 d3.select(this).select('.right')
5551 .attr('x', x2(d[1]))
5552 .attr('width', rightWidth < 0 ? 0 : rightWidth);
5556 function onBrush() {
5557 brushExtent = brush.empty() ? null : brush.extent();
5558 extent = brush.empty() ? x2.domain() : brush.extent();
5559 dispatch.brush({extent: extent, brush: brush});
5562 // Prepare Main (Focus) Bars and Lines
5564 .width(availableWidth)
5565 .height(availableHeight1)
5566 .color(data.map(function(d,i) {
5567 return d.color || color(d, i);
5568 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
5571 .width(availableWidth)
5572 .height(availableHeight1)
5573 .color(data.map(function(d,i) {
5574 return d.color || color(d, i);
5575 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
5577 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
5578 .datum(!dataBars.length ? [{values:[]}] :
5580 .map(function(d,i) {
5583 values: d.values.filter(function(d,i) {
5584 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
5590 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
5591 .datum(dataLines[0].disabled ? [{values:[]}] :
5593 .map(function(d,i) {
5596 values: d.values.filter(function(d,i) {
5597 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
5603 // Update Main (Focus) X Axis
5604 if (dataBars.length) {
5612 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5613 .tickSize(-availableHeight1, 0);
5615 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
5617 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
5620 // Update Main (Focus) Bars and Lines
5621 focusBarsWrap.transition().duration(transitionDuration).call(bars);
5622 focusLinesWrap.transition().duration(transitionDuration).call(lines);
5624 // Setup and Update Main (Focus) Y Axes
5625 g.select('.nv-focus .nv-x.nv-axis')
5626 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
5630 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
5631 .tickSize(-availableWidth, 0);
5634 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
5635 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
5637 g.select('.nv-focus .nv-y1.nv-axis')
5638 .style('opacity', dataBars.length ? 1 : 0);
5639 g.select('.nv-focus .nv-y2.nv-axis')
5640 .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
5641 .attr('transform', 'translate(' + x.range()[1] + ',0)');
5643 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
5645 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
5656 //============================================================
5657 // Event Handling/Dispatching (out of chart's scope)
5658 //------------------------------------------------------------
5660 lines.dispatch.on('elementMouseover.tooltip', function(e) {
5661 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5662 dispatch.tooltipShow(e);
5665 lines.dispatch.on('elementMouseout.tooltip', function(e) {
5666 dispatch.tooltipHide(e);
5669 bars.dispatch.on('elementMouseover.tooltip', function(e) {
5670 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5671 dispatch.tooltipShow(e);
5674 bars.dispatch.on('elementMouseout.tooltip', function(e) {
5675 dispatch.tooltipHide(e);
5678 dispatch.on('tooltipHide', function() {
5679 if (tooltips) nv.tooltip.cleanup();
5682 //============================================================
5685 //============================================================
5686 // Expose Public Variables
5687 //------------------------------------------------------------
5689 // expose chart's sub-components
5690 chart.dispatch = dispatch;
5691 chart.legend = legend;
5692 chart.lines = lines;
5693 chart.lines2 = lines2;
5695 chart.bars2 = bars2;
5696 chart.xAxis = xAxis;
5697 chart.x2Axis = x2Axis;
5698 chart.y1Axis = y1Axis;
5699 chart.y2Axis = y2Axis;
5700 chart.y3Axis = y3Axis;
5701 chart.y4Axis = y4Axis;
5703 chart.options = nv.utils.optionsFunc.bind(chart);
5705 chart._options = Object.create({}, {
5706 // simple options, just get/set the necessary values
5707 width: {get: function(){return width;}, set: function(_){width=_;}},
5708 height: {get: function(){return height;}, set: function(_){height=_;}},
5709 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
5710 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
5711 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
5712 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
5713 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
5714 focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
5715 focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
5716 focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
5717 focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
5719 // options that require extra logic in the setter
5720 margin: {get: function(){return margin;}, set: function(_){
5721 margin.top = _.top !== undefined ? _.top : margin.top;
5722 margin.right = _.right !== undefined ? _.right : margin.right;
5723 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5724 margin.left = _.left !== undefined ? _.left : margin.left;
5726 duration: {get: function(){return transitionDuration;}, set: function(_){
5727 transitionDuration = _;
5729 color: {get: function(){return color;}, set: function(_){
5730 color = nv.utils.getColor(_);
5731 legend.color(color);
5733 x: {get: function(){return getX;}, set: function(_){
5740 y: {get: function(){return getY;}, set: function(_){
5749 nv.utils.inheritOptions(chart, lines);
5750 nv.utils.initOptions(chart);
5754 nv.models.lineWithFocusChart = function() {
5757 //============================================================
5758 // Public Variables with Default Settings
5759 //------------------------------------------------------------
5761 var lines = nv.models.line()
5762 , lines2 = nv.models.line()
5763 , xAxis = nv.models.axis()
5764 , yAxis = nv.models.axis()
5765 , x2Axis = nv.models.axis()
5766 , y2Axis = nv.models.axis()
5767 , legend = nv.models.legend()
5768 , brush = d3.svg.brush()
5771 var margin = {top: 30, right: 30, bottom: 30, left: 60}
5772 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
5773 , color = nv.utils.defaultColor()
5782 , brushExtent = null
5784 , tooltip = function(key, x, y, e, graph) {
5785 return '<h3>' + key + '</h3>' +
5786 '<p>' + y + ' at ' + x + '</p>'
5788 , noData = "No Data Available."
5789 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush', 'stateChange', 'changeState')
5790 , transitionDuration = 250
5791 , state = nv.utils.state()
5792 , defaultState = null
5816 //============================================================
5817 // Private Variables
5818 //------------------------------------------------------------
5820 var showTooltip = function(e, offsetElement) {
5821 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5822 top = e.pos[1] + ( offsetElement.offsetTop || 0),
5823 x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5824 y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5825 content = tooltip(e.series.key, x, y, e, chart);
5827 nv.tooltip.show([left, top], content, null, null, offsetElement);
5830 var stateGetter = function(data) {
5833 active: data.map(function(d) { return !d.disabled })
5838 var stateSetter = function(data) {
5839 return function(state) {
5840 if (state.active !== undefined)
5841 data.forEach(function(series,i) {
5842 series.disabled = !state.active[i];
5847 function chart(selection) {
5848 selection.each(function(data) {
5849 var container = d3.select(this),
5851 nv.utils.initSVG(container);
5852 var availableWidth = (width || parseInt(container.style('width')) || 960)
5853 - margin.left - margin.right,
5854 availableHeight1 = (height || parseInt(container.style('height')) || 400)
5855 - margin.top - margin.bottom - height2,
5856 availableHeight2 = height2 - margin2.top - margin2.bottom;
5858 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5859 chart.container = this;
5862 .setter(stateSetter(data), chart.update)
5863 .getter(stateGetter(data))
5866 // DEPRECATED set state.disableddisabled
5867 state.disabled = data.map(function(d) { return !!d.disabled });
5869 if (!defaultState) {
5872 for (key in state) {
5873 if (state[key] instanceof Array)
5874 defaultState[key] = state[key].slice(0);
5876 defaultState[key] = state[key];
5880 // Display No Data message if there's nothing to show.
5881 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5882 var noDataText = container.selectAll('.nv-noData').data([noData]);
5884 noDataText.enter().append('text')
5885 .attr('class', 'nvd3 nv-noData')
5886 .attr('dy', '-.7em')
5887 .style('text-anchor', 'middle');
5890 .attr('x', margin.left + availableWidth / 2)
5891 .attr('y', margin.top + availableHeight1 / 2)
5892 .text(function(d) { return d });
5896 container.selectAll('.nv-noData').remove();
5902 x2 = lines2.xScale();
5903 y2 = lines2.yScale();
5905 // Setup containers and skeleton of chart
5906 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
5907 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
5908 var g = wrap.select('g');
5910 gEnter.append('g').attr('class', 'nv-legendWrap');
5912 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
5913 focusEnter.append('g').attr('class', 'nv-x nv-axis');
5914 focusEnter.append('g').attr('class', 'nv-y nv-axis');
5915 focusEnter.append('g').attr('class', 'nv-linesWrap');
5917 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
5918 contextEnter.append('g').attr('class', 'nv-x nv-axis');
5919 contextEnter.append('g').attr('class', 'nv-y nv-axis');
5920 contextEnter.append('g').attr('class', 'nv-linesWrap');
5921 contextEnter.append('g').attr('class', 'nv-brushBackground');
5922 contextEnter.append('g').attr('class', 'nv-x nv-brush');
5926 legend.width(availableWidth);
5928 g.select('.nv-legendWrap')
5932 if ( margin.top != legend.height()) {
5933 margin.top = legend.height();
5934 availableHeight1 = (height || parseInt(container.style('height')) || 400)
5935 - margin.top - margin.bottom - height2;
5938 g.select('.nv-legendWrap')
5939 .attr('transform', 'translate(0,' + (-margin.top) +')')
5942 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5944 // Main Chart Component(s)
5946 .width(availableWidth)
5947 .height(availableHeight1)
5950 .map(function(d,i) {
5951 return d.color || color(d, i);
5953 .filter(function(d,i) {
5954 return !data[i].disabled;
5959 .defined(lines.defined())
5960 .width(availableWidth)
5961 .height(availableHeight2)
5964 .map(function(d,i) {
5965 return d.color || color(d, i);
5967 .filter(function(d,i) {
5968 return !data[i].disabled;
5972 g.select('.nv-context')
5973 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
5975 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
5976 .datum(data.filter(function(d) { return !d.disabled }))
5978 d3.transition(contextLinesWrap).call(lines2);
5980 // Setup Main (Focus) Axes
5983 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5984 .tickSize(-availableHeight1, 0);
5988 .ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
5989 .tickSize( -availableWidth, 0);
5991 g.select('.nv-focus .nv-x.nv-axis')
5992 .attr('transform', 'translate(0,' + availableHeight1 + ')');
5997 .on('brush', function() {
5998 //When brushing, turn off transitions because chart needs to change immediately.
5999 var oldTransition = chart.duration();
6002 chart.duration(oldTransition);
6005 if (brushExtent) brush.extent(brushExtent);
6007 var brushBG = g.select('.nv-brushBackground').selectAll('g')
6008 .data([brushExtent || brush.extent()])
6010 var brushBGenter = brushBG.enter()
6013 brushBGenter.append('rect')
6014 .attr('class', 'left')
6017 .attr('height', availableHeight2);
6019 brushBGenter.append('rect')
6020 .attr('class', 'right')
6023 .attr('height', availableHeight2);
6025 var gBrush = g.select('.nv-x.nv-brush')
6027 gBrush.selectAll('rect')
6029 .attr('height', availableHeight2);
6030 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6034 // Setup Secondary (Context) Axes
6037 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6038 .tickSize(-availableHeight2, 0);
6040 g.select('.nv-context .nv-x.nv-axis')
6041 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6042 d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6047 .ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
6048 .tickSize( -availableWidth, 0);
6050 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
6053 g.select('.nv-context .nv-x.nv-axis')
6054 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
6056 //============================================================
6057 // Event Handling/Dispatching (in chart's scope)
6058 //------------------------------------------------------------
6060 legend.dispatch.on('stateChange', function(newState) {
6061 for (var key in newState)
6062 state[key] = newState[key];
6063 dispatch.stateChange(state);
6067 dispatch.on('tooltipShow', function(e) {
6068 if (tooltips) showTooltip(e, that.parentNode);
6071 dispatch.on('changeState', function(e) {
6072 if (typeof e.disabled !== 'undefined') {
6073 data.forEach(function(series,i) {
6074 series.disabled = e.disabled[i];
6080 //============================================================
6082 //------------------------------------------------------------
6084 // Taken from crossfilter (http://square.github.com/crossfilter/)
6085 function resizePath(d) {
6086 var e = +(d == 'e'),
6088 y = availableHeight2 / 3;
6089 return 'M' + (.5 * x) + ',' + y
6090 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6092 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6094 + 'M' + (2.5 * x) + ',' + (y + 8)
6096 + 'M' + (4.5 * x) + ',' + (y + 8)
6097 + 'V' + (2 * y - 8);
6101 function updateBrushBG() {
6102 if (!brush.empty()) brush.extent(brushExtent);
6104 .data([brush.empty() ? x2.domain() : brushExtent])
6105 .each(function(d,i) {
6106 var leftWidth = x2(d[0]) - x.range()[0],
6107 rightWidth = x.range()[1] - x2(d[1]);
6108 d3.select(this).select('.left')
6109 .attr('width', leftWidth < 0 ? 0 : leftWidth);
6111 d3.select(this).select('.right')
6112 .attr('x', x2(d[1]))
6113 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6118 function onBrush() {
6119 brushExtent = brush.empty() ? null : brush.extent();
6120 var extent = brush.empty() ? x2.domain() : brush.extent();
6122 //The brush extent cannot be less than one. If it is, don't update the line chart.
6123 if (Math.abs(extent[0] - extent[1]) <= 1) {
6127 dispatch.brush({extent: extent, brush: brush});
6132 // Update Main (Focus)
6133 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6136 .filter(function(d) { return !d.disabled })
6137 .map(function(d,i) {
6141 values: d.values.filter(function(d,i) {
6142 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6147 focusLinesWrap.transition().duration(transitionDuration).call(lines);
6150 // Update Main (Focus) Axes
6151 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
6153 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
6161 //============================================================
6162 // Event Handling/Dispatching (out of chart's scope)
6163 //------------------------------------------------------------
6165 lines.dispatch.on('elementMouseover.tooltip', function(e) {
6166 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6167 dispatch.tooltipShow(e);
6170 lines.dispatch.on('elementMouseout.tooltip', function(e) {
6171 dispatch.tooltipHide(e);
6174 dispatch.on('tooltipHide', function() {
6175 if (tooltips) nv.tooltip.cleanup();
6178 //============================================================
6179 // Expose Public Variables
6180 //------------------------------------------------------------
6182 // expose chart's sub-components
6183 chart.dispatch = dispatch;
6184 chart.legend = legend;
6185 chart.lines = lines;
6186 chart.lines2 = lines2;
6187 chart.xAxis = xAxis;
6188 chart.yAxis = yAxis;
6189 chart.x2Axis = x2Axis;
6190 chart.y2Axis = y2Axis;
6192 chart.options = nv.utils.optionsFunc.bind(chart);
6194 chart._options = Object.create({}, {
6195 // simple options, just get/set the necessary values
6196 width: {get: function(){return width;}, set: function(_){width=_;}},
6197 height: {get: function(){return height;}, set: function(_){height=_;}},
6198 focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}},
6199 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6200 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
6201 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
6202 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
6203 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
6204 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
6206 // options that require extra logic in the setter
6207 margin: {get: function(){return margin;}, set: function(_){
6208 margin.top = _.top !== undefined ? _.top : margin.top;
6209 margin.right = _.right !== undefined ? _.right : margin.right;
6210 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6211 margin.left = _.left !== undefined ? _.left : margin.left;
6213 color: {get: function(){return color;}, set: function(_){
6214 color = nv.utils.getColor(_);
6215 legend.color(color);
6216 // line color is handled above?
6218 interpolate: {get: function(){return lines.interpolate();}, set: function(_){
6219 lines.interpolate(_);
6220 lines2.interpolate(_);
6222 xTickFormat: {get: function(){return xAxis.xTickFormat();}, set: function(_){
6223 xAxis.xTickFormat(_);
6224 x2Axis.xTickFormat(_);
6226 yTickFormat: {get: function(){return yAxis.yTickFormat();}, set: function(_){
6227 yAxis.yTickFormat(_);
6228 y2Axis.yTickFormat(_);
6230 duration: {get: function(){return transitionDuration;}, set: function(_){
6231 transitionDuration=_;
6232 yAxis.duration(transitionDuration);
6233 xAxis.duration(transitionDuration);
6235 x: {get: function(){return lines.x();}, set: function(_){
6239 y: {get: function(){return lines.y();}, set: function(_){
6245 nv.utils.inheritOptions(chart, lines);
6246 nv.utils.initOptions(chart);
6251 nv.models.multiBar = function() {
6254 //============================================================
6255 // Public Variables with Default Settings
6256 //------------------------------------------------------------
6258 var margin = {top: 0, right: 0, bottom: 0, left: 0}
6261 , x = d3.scale.ordinal()
6262 , y = d3.scale.linear()
6263 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
6264 , getX = function(d) { return d.x }
6265 , getY = function(d) { return d.y }
6266 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
6269 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
6270 , color = nv.utils.defaultColor()
6272 , barColor = null // adding the ability to set the color for each rather than the whole group
6273 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
6279 , groupSpacing = 0.1
6280 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
6283 //============================================================
6284 // Private Variables
6285 //------------------------------------------------------------
6287 var x0, y0 //used to store previous scales
6288 , renderWatch = nv.utils.renderWatch(dispatch, duration)
6291 var last_datalength = 0;
6293 function chart(selection) {
6294 renderWatch.reset();
6295 selection.each(function(data) {
6296 var availableWidth = width - margin.left - margin.right,
6297 availableHeight = height - margin.top - margin.bottom,
6298 container = d3.select(this);
6299 nv.utils.initSVG(container);
6301 // This function defines the requirements for render complete
6302 var endFn = function(d, i) {
6303 if (d.series === data.length - 1 && i === data[0].values.length - 1)
6308 if(hideable && data.length) hideable = [{
6309 values: data[0].values.map(function(d) {
6319 data = d3.layout.stack()
6320 .offset(stackOffset)
6321 .values(function(d){ return d.values })
6323 (!data.length && hideable ? hideable : data);
6326 //add series index to each data point for reference
6327 data.forEach(function(series, i) {
6328 series.values.forEach(function(point) {
6333 // HACK for negative value stacking
6335 data[0].values.map(function(d,i) {
6336 var posBase = 0, negBase = 0;
6337 data.map(function(d) {
6339 f.size = Math.abs(f.y);
6342 negBase = negBase - f.size;
6345 f.y1 = f.size + posBase;
6346 posBase = posBase + f.size;
6352 // remap and flatten the data for use in calculating the scales' domains
6353 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
6354 data.map(function(d) {
6355 return d.values.map(function(d,i) {
6356 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
6360 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
6361 .rangeBands(xRange || [0, availableWidth], groupSpacing);
6363 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)))
6364 .range(yRange || [availableHeight, 0]);
6366 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
6367 if (x.domain()[0] === x.domain()[1])
6369 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
6372 if (y.domain()[0] === y.domain()[1])
6374 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
6380 // Setup containers and skeleton of chart
6381 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
6382 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
6383 var defsEnter = wrapEnter.append('defs');
6384 var gEnter = wrapEnter.append('g');
6385 var g = wrap.select('g')
6387 gEnter.append('g').attr('class', 'nv-groups');
6388 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6390 defsEnter.append('clipPath')
6391 .attr('id', 'nv-edge-clip-' + id)
6393 wrap.select('#nv-edge-clip-' + id + ' rect')
6394 .attr('width', availableWidth)
6395 .attr('height', availableHeight);
6397 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
6399 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
6400 .data(function(d) { return d }, function(d,i) { return i });
6401 groups.enter().append('g')
6402 .style('stroke-opacity', 1e-6)
6403 .style('fill-opacity', 1e-6);
6405 var exitTransition = renderWatch
6406 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
6407 .attr('y', function(d) { return (stacked ? y0(d.y0) : y0(0)) || 0 })
6410 if (exitTransition.delay)
6411 exitTransition.delay(function(d,i) {
6412 var delay = i * (duration / (last_datalength + 1)) - i;
6416 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
6417 .classed('hover', function(d) { return d.hover })
6418 .style('fill', function(d,i){ return color(d, i) })
6419 .style('stroke', function(d,i){ return color(d, i) });
6421 .style('stroke-opacity', 1)
6422 .style('fill-opacity', 0.75);
6424 var bars = groups.selectAll('rect.nv-bar')
6425 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
6426 bars.exit().remove();
6428 var barsEnter = bars.enter().append('rect')
6429 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
6430 .attr('x', function(d,i,j) {
6431 return stacked ? 0 : (j * x.rangeBand() / data.length )
6433 .attr('y', function(d) { return y0(stacked ? d.y0 : 0) || 0 })
6435 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
6436 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
6439 .style('fill', function(d,i,j){ return color(d, j, i); })
6440 .style('stroke', function(d,i,j){ return color(d, j, i); })
6441 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
6442 d3.select(this).classed('hover', true);
6443 dispatch.elementMouseover({
6446 series: data[d.series],
6447 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
6449 seriesIndex: d.series,
6453 .on('mouseout', function(d,i) {
6454 d3.select(this).classed('hover', false);
6455 dispatch.elementMouseout({
6458 series: data[d.series],
6460 seriesIndex: d.series,
6464 .on('click', function(d,i) {
6465 dispatch.elementClick({
6468 series: data[d.series],
6469 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
6471 seriesIndex: d.series,
6474 d3.event.stopPropagation();
6476 .on('dblclick', function(d,i) {
6477 dispatch.elementDblClick({
6480 series: data[d.series],
6481 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
6483 seriesIndex: d.series,
6486 d3.event.stopPropagation();
6489 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
6490 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
6493 if (!disabled) disabled = data.map(function() { return true });
6495 .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(); })
6496 .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(); });
6500 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
6501 .delay(function(d,i) {
6502 return i * duration / data[0].values.length;
6506 .attr('y', function(d,i) {
6507 return y((stacked ? d.y1 : 0));
6509 .attr('height', function(d,i) {
6510 return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
6512 .attr('x', function(d,i) {
6513 return stacked ? 0 : (d.series * x.rangeBand() / data.length )
6515 .attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
6518 .attr('x', function(d,i) {
6519 return d.series * x.rangeBand() / data.length
6521 .attr('width', x.rangeBand() / data.length)
6522 .attr('y', function(d,i) {
6523 return getY(d,i) < 0 ?
6525 y(0) - y(getY(d,i)) < 1 ?
6529 .attr('height', function(d,i) {
6530 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
6533 //store old scales for use in transitions on update
6537 // keep track of the last data value length for transition calculations
6538 if (data[0] && data[0].values) {
6539 last_datalength = data[0].values.length;
6544 renderWatch.renderEnd('multibar immediate');
6549 //============================================================
6550 // Expose Public Variables
6551 //------------------------------------------------------------
6553 chart.dispatch = dispatch;
6555 chart.options = nv.utils.optionsFunc.bind(chart);
6557 chart._options = Object.create({}, {
6558 // simple options, just get/set the necessary values
6559 width: {get: function(){return width;}, set: function(_){width=_;}},
6560 height: {get: function(){return height;}, set: function(_){height=_;}},
6561 x: {get: function(){return getX;}, set: function(_){getX=_;}},
6562 y: {get: function(){return getY;}, set: function(_){getY=_;}},
6563 xScale: {get: function(){return x;}, set: function(_){x=_;}},
6564 yScale: {get: function(){return y;}, set: function(_){y=_;}},
6565 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
6566 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
6567 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
6568 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
6569 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
6570 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
6571 stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
6572 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
6573 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
6574 id: {get: function(){return id;}, set: function(_){id=_;}},
6575 hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
6576 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
6578 // options that require extra logic in the setter
6579 margin: {get: function(){return margin;}, set: function(_){
6580 margin.top = _.top !== undefined ? _.top : margin.top;
6581 margin.right = _.right !== undefined ? _.right : margin.right;
6582 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6583 margin.left = _.left !== undefined ? _.left : margin.left;
6585 duration: {get: function(){return duration;}, set: function(_){
6587 renderWatch.reset(duration);
6589 color: {get: function(){return color;}, set: function(_){
6590 color = nv.utils.getColor(_);
6592 barColor: {get: function(){return barColor;}, set: function(_){
6593 barColor = nv.utils.getColor(_);
6597 nv.utils.initOptions(chart);
6602 nv.models.multiBarChart = function() {
6605 //============================================================
6606 // Public Variables with Default Settings
6607 //------------------------------------------------------------
6609 var multibar = nv.models.multiBar()
6610 , xAxis = nv.models.axis()
6611 , yAxis = nv.models.axis()
6612 , legend = nv.models.legend()
6613 , controls = nv.models.legend()
6616 var margin = {top: 30, right: 20, bottom: 50, left: 60}
6619 , color = nv.utils.defaultColor()
6620 , showControls = true
6624 , rightAlignYAxis = false
6625 , reduceXTicks = true // if false a tick will show for every data point
6626 , staggerLabels = false
6629 , tooltip = function(key, x, y, e, graph) {
6630 return '<h3>' + key + '</h3>' +
6631 '<p>' + y + ' on ' + x + '</p>'
6633 , x //can be accessed via chart.xScale()
6634 , y //can be accessed via chart.yScale()
6635 , state = nv.utils.state()
6636 , defaultState = null
6637 , noData = "No Data Available."
6638 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
6639 , controlWidth = function() { return showControls ? 180 : 0 }
6643 state.stacked = false // DEPRECATED Maintained for backward compatibility
6651 .highlightZero(true)
6653 .tickFormat(function(d) { return d })
6656 .orient((rightAlignYAxis) ? 'right' : 'left')
6657 .tickFormat(d3.format(',.1f'))
6660 controls.updateState(false);
6662 //============================================================
6663 // Private Variables
6664 //------------------------------------------------------------
6666 var renderWatch = nv.utils.renderWatch(dispatch);
6667 var stacked = false;
6669 var showTooltip = function(e, offsetElement) {
6670 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6671 top = e.pos[1] + ( offsetElement.offsetTop || 0),
6672 x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
6673 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
6674 content = tooltip(e.series.key, x, y, e, chart);
6676 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6679 var stateGetter = function(data) {
6682 active: data.map(function(d) { return !d.disabled }),
6688 var stateSetter = function(data) {
6689 return function(state) {
6690 if (state.stacked !== undefined)
6691 stacked = state.stacked;
6692 if (state.active !== undefined)
6693 data.forEach(function(series,i) {
6694 series.disabled = !state.active[i];
6699 function chart(selection) {
6700 renderWatch.reset();
6701 renderWatch.models(multibar);
6702 if (showXAxis) renderWatch.models(xAxis);
6703 if (showYAxis) renderWatch.models(yAxis);
6705 selection.each(function(data) {
6706 var container = d3.select(this),
6708 nv.utils.initSVG(container);
6709 var availableWidth = (width || parseInt(container.style('width')) || 960)
6710 - margin.left - margin.right,
6711 availableHeight = (height || parseInt(container.style('height')) || 400)
6712 - margin.top - margin.bottom;
6714 chart.update = function() {
6716 container.call(chart);
6718 container.transition()
6722 chart.container = this;
6725 .setter(stateSetter(data), chart.update)
6726 .getter(stateGetter(data))
6729 // DEPRECATED set state.disableddisabled
6730 state.disabled = data.map(function(d) { return !!d.disabled });
6732 if (!defaultState) {
6735 for (key in state) {
6736 if (state[key] instanceof Array)
6737 defaultState[key] = state[key].slice(0);
6739 defaultState[key] = state[key];
6743 // Display noData message if there's nothing to show.
6744 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6745 var noDataText = container.selectAll('.nv-noData').data([noData]);
6747 noDataText.enter().append('text')
6748 .attr('class', 'nvd3 nv-noData')
6749 .attr('dy', '-.7em')
6750 .style('text-anchor', 'middle');
6753 .attr('x', margin.left + availableWidth / 2)
6754 .attr('y', margin.top + availableHeight / 2)
6755 .text(function(d) { return d });
6759 container.selectAll('.nv-noData').remove();
6763 x = multibar.xScale();
6764 y = multibar.yScale();
6766 // Setup containers and skeleton of chart
6767 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
6768 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
6769 var g = wrap.select('g');
6771 gEnter.append('g').attr('class', 'nv-x nv-axis');
6772 gEnter.append('g').attr('class', 'nv-y nv-axis');
6773 gEnter.append('g').attr('class', 'nv-barsWrap');
6774 gEnter.append('g').attr('class', 'nv-legendWrap');
6775 gEnter.append('g').attr('class', 'nv-controlsWrap');
6779 legend.width(availableWidth - controlWidth());
6781 if (multibar.barColor())
6782 data.forEach(function(series,i) {
6783 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
6786 g.select('.nv-legendWrap')
6790 if ( margin.top != legend.height()) {
6791 margin.top = legend.height();
6792 availableHeight = (height || parseInt(container.style('height')) || 400)
6793 - margin.top - margin.bottom;
6796 g.select('.nv-legendWrap')
6797 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
6802 var controlsData = [
6803 { key: 'Grouped', disabled: multibar.stacked() },
6804 { key: 'Stacked', disabled: !multibar.stacked() }
6807 controls.width(controlWidth()).color(['#444', '#444', '#444']);
6808 g.select('.nv-controlsWrap')
6809 .datum(controlsData)
6810 .attr('transform', 'translate(0,' + (-margin.top) +')')
6814 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6815 if (rightAlignYAxis) {
6816 g.select(".nv-y.nv-axis")
6817 .attr("transform", "translate(" + availableWidth + ",0)");
6820 // Main Chart Component(s)
6822 .disabled(data.map(function(series) { return series.disabled }))
6823 .width(availableWidth)
6824 .height(availableHeight)
6825 .color(data.map(function(d,i) {
6826 return d.color || color(d, i);
6827 }).filter(function(d,i) { return !data[i].disabled }));
6830 var barsWrap = g.select('.nv-barsWrap')
6831 .datum(data.filter(function(d) { return !d.disabled }));
6833 barsWrap.call(multibar);
6839 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6840 .tickSize(-availableHeight, 0);
6842 g.select('.nv-x.nv-axis')
6843 .attr('transform', 'translate(0,' + y.range()[0] + ')');
6844 g.select('.nv-x.nv-axis')
6847 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
6850 .selectAll('line, text')
6851 .style('opacity', 1)
6853 if (staggerLabels) {
6854 var getTranslate = function(x,y) {
6855 return "translate(" + x + "," + y + ")";
6858 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
6862 .attr('transform', function(d,i,j) {
6863 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
6866 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
6867 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
6868 .attr("transform", function(d,i) {
6869 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
6875 .filter(function(d,i) {
6876 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
6878 .selectAll('text, line')
6879 .style('opacity', 0);
6883 .selectAll('.tick text')
6884 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
6885 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
6887 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
6888 .style('opacity', 1);
6894 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
6895 .tickSize( -availableWidth, 0);
6897 g.select('.nv-y.nv-axis')
6901 //============================================================
6902 // Event Handling/Dispatching (in chart's scope)
6903 //------------------------------------------------------------
6905 legend.dispatch.on('stateChange', function(newState) {
6906 for (var key in newState)
6907 state[key] = newState[key];
6908 dispatch.stateChange(state);
6912 controls.dispatch.on('legendClick', function(d,i) {
6913 if (!d.disabled) return;
6914 controlsData = controlsData.map(function(s) {
6922 multibar.stacked(false);
6925 multibar.stacked(true);
6929 state.stacked = multibar.stacked();
6930 dispatch.stateChange(state);
6935 dispatch.on('tooltipShow', function(e) {
6936 if (tooltips) showTooltip(e, that.parentNode)
6939 // Update chart from a state object passed to event handler
6940 dispatch.on('changeState', function(e) {
6942 if (typeof e.disabled !== 'undefined') {
6943 data.forEach(function(series,i) {
6944 series.disabled = e.disabled[i];
6947 state.disabled = e.disabled;
6950 if (typeof e.stacked !== 'undefined') {
6951 multibar.stacked(e.stacked);
6952 state.stacked = e.stacked;
6953 stacked = e.stacked;
6960 renderWatch.renderEnd('multibarchart immediate');
6964 //============================================================
6965 // Event Handling/Dispatching (out of chart's scope)
6966 //------------------------------------------------------------
6968 multibar.dispatch.on('elementMouseover.tooltip', function(e) {
6969 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6970 dispatch.tooltipShow(e);
6973 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
6974 dispatch.tooltipHide(e);
6976 dispatch.on('tooltipHide', function() {
6977 if (tooltips) nv.tooltip.cleanup();
6980 //============================================================
6981 // Expose Public Variables
6982 //------------------------------------------------------------
6984 // expose chart's sub-components
6985 chart.dispatch = dispatch;
6986 chart.multibar = multibar;
6987 chart.legend = legend;
6988 chart.xAxis = xAxis;
6989 chart.yAxis = yAxis;
6990 chart.state = state;
6992 chart.options = nv.utils.optionsFunc.bind(chart);
6994 chart._options = Object.create({}, {
6995 // simple options, just get/set the necessary values
6996 width: {get: function(){return width;}, set: function(_){width=_;}},
6997 height: {get: function(){return height;}, set: function(_){height=_;}},
6998 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6999 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
7000 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
7001 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
7002 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
7003 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
7004 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
7005 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
7006 reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
7007 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
7008 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
7010 // options that require extra logic in the setter
7011 margin: {get: function(){return margin;}, set: function(_){
7012 margin.top = _.top !== undefined ? _.top : margin.top;
7013 margin.right = _.right !== undefined ? _.right : margin.right;
7014 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7015 margin.left = _.left !== undefined ? _.left : margin.left;
7017 duration: {get: function(){return duration;}, set: function(_){
7019 multibar.duration(duration);
7020 xAxis.duration(duration);
7021 yAxis.duration(duration);
7022 renderWatch.reset(duration);
7024 color: {get: function(){return color;}, set: function(_){
7025 color = nv.utils.getColor(_);
7026 legend.color(color);
7028 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
7029 rightAlignYAxis = _;
7030 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
7034 nv.utils.inheritOptions(chart, multibar);
7035 nv.utils.initOptions(chart);
7040 nv.models.multiBarHorizontal = function() {
7043 //============================================================
7044 // Public Variables with Default Settings
7045 //------------------------------------------------------------
7047 var margin = {top: 0, right: 0, bottom: 0, left: 0}
7050 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7051 , x = d3.scale.ordinal()
7052 , y = d3.scale.linear()
7053 , getX = function(d) { return d.x }
7054 , getY = function(d) { return d.y }
7055 , getYerr = function(d) { return d.yErr }
7056 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7057 , color = nv.utils.defaultColor()
7058 , barColor = null // adding the ability to set the color for each rather than the whole group
7059 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7061 , showValues = false
7062 , showBarLabels = false
7064 , valueFormat = d3.format(',.2f')
7071 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout','renderEnd')
7074 //============================================================
7075 // Private Variables
7076 //------------------------------------------------------------
7078 var x0, y0; //used to store previous scales
7079 var renderWatch = nv.utils.renderWatch(dispatch, duration);
7081 function chart(selection) {
7082 renderWatch.reset();
7083 selection.each(function(data) {
7084 var availableWidth = width - margin.left - margin.right,
7085 availableHeight = height - margin.top - margin.bottom,
7086 container = d3.select(this);
7087 nv.utils.initSVG(container);
7090 data = d3.layout.stack()
7092 .values(function(d){ return d.values })
7096 //add series index to each data point for reference
7097 data.forEach(function(series, i) {
7098 series.values.forEach(function(point) {
7103 // HACK for negative value stacking
7105 data[0].values.map(function(d,i) {
7106 var posBase = 0, negBase = 0;
7107 data.map(function(d) {
7109 f.size = Math.abs(f.y);
7111 f.y1 = negBase - f.size;
7112 negBase = negBase - f.size;
7116 posBase = posBase + f.size;
7122 // remap and flatten the data for use in calculating the scales' domains
7123 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7124 data.map(function(d) {
7125 return d.values.map(function(d,i) {
7126 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7130 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7131 .rangeBands(xRange || [0, availableHeight], .1);
7133 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)))
7135 if (showValues && !stacked)
7136 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
7138 y.range(yRange || [0, availableWidth]);
7141 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
7143 // Setup containers and skeleton of chart
7144 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
7145 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
7146 var defsEnter = wrapEnter.append('defs');
7147 var gEnter = wrapEnter.append('g');
7148 var g = wrap.select('g');
7150 gEnter.append('g').attr('class', 'nv-groups');
7151 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7153 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7154 .data(function(d) { return d }, function(d,i) { return i });
7155 groups.enter().append('g')
7156 .style('stroke-opacity', 1e-6)
7157 .style('fill-opacity', 1e-6);
7158 groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
7159 .style('stroke-opacity', 1e-6)
7160 .style('fill-opacity', 1e-6)
7163 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7164 .classed('hover', function(d) { return d.hover })
7165 .style('fill', function(d,i){ return color(d, i) })
7166 .style('stroke', function(d,i){ return color(d, i) });
7167 groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
7168 .style('stroke-opacity', 1)
7169 .style('fill-opacity', .75);
7171 var bars = groups.selectAll('g.nv-bar')
7172 .data(function(d) { return d.values });
7173 bars.exit().remove();
7175 var barsEnter = bars.enter().append('g')
7176 .attr('transform', function(d,i,j) {
7177 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
7180 barsEnter.append('rect')
7182 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
7185 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7186 d3.select(this).classed('hover', true);
7187 dispatch.elementMouseover({
7190 series: data[d.series],
7191 pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
7193 seriesIndex: d.series,
7197 .on('mouseout', function(d,i) {
7198 d3.select(this).classed('hover', false);
7199 dispatch.elementMouseout({
7202 series: data[d.series],
7204 seriesIndex: d.series,
7208 .on('click', function(d,i) {
7209 dispatch.elementClick({
7212 series: data[d.series],
7213 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
7215 seriesIndex: d.series,
7218 d3.event.stopPropagation();
7220 .on('dblclick', function(d,i) {
7221 dispatch.elementDblClick({
7224 series: data[d.series],
7225 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
7227 seriesIndex: d.series,
7230 d3.event.stopPropagation();
7233 if (getYerr(data[0],0)) {
7234 barsEnter.append('polyline');
7236 bars.select('polyline')
7237 .attr('fill', 'none')
7238 .attr('points', function(d,i) {
7239 var xerr = getYerr(d,i)
7240 , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
7241 xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
7242 xerr = xerr.map(function(e) { return y(e) - y(0); });
7243 var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
7244 return a.map(function (path) { return path.join(',') }).join(' ');
7246 .attr('transform', function(d,i) {
7247 var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
7248 return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
7252 barsEnter.append('text');
7254 if (showValues && !stacked) {
7256 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
7257 .attr('y', x.rangeBand() / (data.length * 2))
7258 .attr('dy', '.32em')
7259 .html(function(d,i) {
7260 var t = valueFormat(getY(d,i))
7261 , yerr = getYerr(d,i);
7262 if (yerr === undefined)
7265 return t + '±' + valueFormat(Math.abs(yerr));
7266 return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
7268 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7270 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
7272 bars.selectAll('text').text('');
7275 if (showBarLabels && !stacked) {
7276 barsEnter.append('text').classed('nv-bar-label',true);
7277 bars.select('text.nv-bar-label')
7278 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
7279 .attr('y', x.rangeBand() / (data.length * 2))
7280 .attr('dy', '.32em')
7281 .text(function(d,i) { return getX(d,i) });
7282 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7283 .select('text.nv-bar-label')
7284 .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
7287 bars.selectAll('text.nv-bar-label').text('');
7291 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7294 if (!disabled) disabled = data.map(function() { return true });
7296 .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(); })
7297 .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(); });
7301 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7302 .attr('transform', function(d,i) {
7303 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
7306 .attr('width', function(d,i) {
7307 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
7309 .attr('height', x.rangeBand() );
7311 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
7312 .attr('transform', function(d,i) {
7313 //TODO: stacked must be all positive or all negative, not both?
7314 return 'translate(' +
7315 (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
7317 (d.series * x.rangeBand() / data.length
7323 .attr('height', x.rangeBand() / data.length )
7324 .attr('width', function(d,i) {
7325 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
7328 //store old scales for use in transitions on update
7334 renderWatch.renderEnd('multibarHorizontal immediate');
7338 //============================================================
7339 // Expose Public Variables
7340 //------------------------------------------------------------
7342 chart.dispatch = dispatch;
7344 chart.options = nv.utils.optionsFunc.bind(chart);
7346 chart._options = Object.create({}, {
7347 // simple options, just get/set the necessary values
7348 width: {get: function(){return width;}, set: function(_){width=_;}},
7349 height: {get: function(){return height;}, set: function(_){height=_;}},
7350 x: {get: function(){return getX;}, set: function(_){getX=_;}},
7351 y: {get: function(){return getY;}, set: function(_){getY=_;}},
7352 yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
7353 xScale: {get: function(){return x;}, set: function(_){x=_;}},
7354 yScale: {get: function(){return y;}, set: function(_){y=_;}},
7355 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
7356 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
7357 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
7358 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
7359 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
7360 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
7361 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
7362 // this shows the group name, seems pointless?
7363 //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
7364 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
7365 id: {get: function(){return id;}, set: function(_){id=_;}},
7366 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
7367 valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
7369 // options that require extra logic in the setter
7370 margin: {get: function(){return margin;}, set: function(_){
7371 margin.top = _.top !== undefined ? _.top : margin.top;
7372 margin.right = _.right !== undefined ? _.right : margin.right;
7373 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7374 margin.left = _.left !== undefined ? _.left : margin.left;
7376 duration: {get: function(){return duration;}, set: function(_){
7378 renderWatch.reset(duration);
7380 color: {get: function(){return color;}, set: function(_){
7381 color = nv.utils.getColor(_);
7383 barColor: {get: function(){return color;}, set: function(_){
7384 barColor = nv.utils.getColor(_);
7388 nv.utils.initOptions(chart);
7392 nv.models.multiBarHorizontalChart = function() {
7395 //============================================================
7396 // Public Variables with Default Settings
7397 //------------------------------------------------------------
7399 var multibar = nv.models.multiBarHorizontal()
7400 , xAxis = nv.models.axis()
7401 , yAxis = nv.models.axis()
7402 , legend = nv.models.legend().height(30)
7403 , controls = nv.models.legend().height(30)
7406 var margin = {top: 30, right: 20, bottom: 50, left: 60}
7409 , color = nv.utils.defaultColor()
7410 , showControls = true
7416 , tooltip = function(key, x, y, e, graph) {
7417 return '<h3>' + key + ' - ' + x + '</h3>' +
7420 , x //can be accessed via chart.xScale()
7421 , y //can be accessed via chart.yScale()
7422 , state = nv.utils.state()
7423 , defaultState = null
7424 , noData = 'No Data Available.'
7425 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
7426 , controlWidth = function() { return showControls ? 180 : 0 }
7430 state.stacked = false; // DEPRECATED Maintained for backward compatibility
7438 .highlightZero(false)
7440 .tickFormat(function(d) { return d })
7444 .tickFormat(d3.format(',.1f'))
7447 controls.updateState(false);
7449 //============================================================
7450 // Private Variables
7451 //------------------------------------------------------------
7453 var showTooltip = function(e, offsetElement) {
7454 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7455 top = e.pos[1] + ( offsetElement.offsetTop || 0),
7456 x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
7457 y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
7458 content = tooltip(e.series.key, x, y, e, chart);
7460 nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
7463 var stateGetter = function(data) {
7466 active: data.map(function(d) { return !d.disabled }),
7472 var stateSetter = function(data) {
7473 return function(state) {
7474 if (state.stacked !== undefined)
7475 stacked = state.stacked;
7476 if (state.active !== undefined)
7477 data.forEach(function(series,i) {
7478 series.disabled = !state.active[i];
7483 var renderWatch = nv.utils.renderWatch(dispatch, duration);
7485 function chart(selection) {
7486 renderWatch.reset();
7487 renderWatch.models(multibar);
7488 if (showXAxis) renderWatch.models(xAxis);
7489 if (showYAxis) renderWatch.models(yAxis);
7491 selection.each(function(data) {
7492 var container = d3.select(this),
7494 nv.utils.initSVG(container);
7495 var availableWidth = (width || parseInt(container.style('width')) || 960)
7496 - margin.left - margin.right,
7497 availableHeight = (height || parseInt(container.style('height')) || 400)
7498 - margin.top - margin.bottom;
7500 chart.update = function() { container.transition().duration(duration).call(chart) };
7501 chart.container = this;
7503 stacked = multibar.stacked();
7506 .setter(stateSetter(data), chart.update)
7507 .getter(stateGetter(data))
7510 // DEPRECATED set state.disableddisabled
7511 state.disabled = data.map(function(d) { return !!d.disabled });
7513 if (!defaultState) {
7516 for (key in state) {
7517 if (state[key] instanceof Array)
7518 defaultState[key] = state[key].slice(0);
7520 defaultState[key] = state[key];
7524 // Display No Data message if there's nothing to show.
7525 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7526 var noDataText = container.selectAll('.nv-noData').data([noData]);
7528 noDataText.enter().append('text')
7529 .attr('class', 'nvd3 nv-noData')
7530 .attr('dy', '-.7em')
7531 .style('text-anchor', 'middle');
7534 .attr('x', margin.left + availableWidth / 2)
7535 .attr('y', margin.top + availableHeight / 2)
7536 .text(function(d) { return d });
7540 container.selectAll('.nv-noData').remove();
7544 x = multibar.xScale();
7545 y = multibar.yScale();
7547 // Setup containers and skeleton of chart
7548 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
7549 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
7550 var g = wrap.select('g');
7552 gEnter.append('g').attr('class', 'nv-x nv-axis');
7553 gEnter.append('g').attr('class', 'nv-y nv-axis')
7554 .append('g').attr('class', 'nv-zeroLine')
7556 gEnter.append('g').attr('class', 'nv-barsWrap');
7557 gEnter.append('g').attr('class', 'nv-legendWrap');
7558 gEnter.append('g').attr('class', 'nv-controlsWrap');
7562 legend.width(availableWidth - controlWidth());
7564 if (multibar.barColor())
7565 data.forEach(function(series,i) {
7566 series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
7569 g.select('.nv-legendWrap')
7573 if ( margin.top != legend.height()) {
7574 margin.top = legend.height();
7575 availableHeight = (height || parseInt(container.style('height')) || 400)
7576 - margin.top - margin.bottom;
7579 g.select('.nv-legendWrap')
7580 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
7585 var controlsData = [
7586 { key: 'Grouped', disabled: multibar.stacked() },
7587 { key: 'Stacked', disabled: !multibar.stacked() }
7590 controls.width(controlWidth()).color(['#444', '#444', '#444']);
7591 g.select('.nv-controlsWrap')
7592 .datum(controlsData)
7593 .attr('transform', 'translate(0,' + (-margin.top) +')')
7597 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7599 // Main Chart Component(s)
7601 .disabled(data.map(function(series) { return series.disabled }))
7602 .width(availableWidth)
7603 .height(availableHeight)
7604 .color(data.map(function(d,i) {
7605 return d.color || color(d, i);
7606 }).filter(function(d,i) { return !data[i].disabled }));
7608 var barsWrap = g.select('.nv-barsWrap')
7609 .datum(data.filter(function(d) { return !d.disabled }));
7611 barsWrap.transition().call(multibar);
7617 .ticks( nv.utils.calcTicksY(availableHeight/24, data) )
7618 .tickSize(-availableWidth, 0);
7620 g.select('.nv-x.nv-axis').call(xAxis);
7622 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
7625 .selectAll('line, text');
7631 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7632 .tickSize( -availableHeight, 0);
7634 g.select('.nv-y.nv-axis')
7635 .attr('transform', 'translate(0,' + availableHeight + ')');
7636 g.select('.nv-y.nv-axis').call(yAxis);
7640 g.select(".nv-zeroLine line")
7644 .attr("y2", -availableHeight)
7647 //============================================================
7648 // Event Handling/Dispatching (in chart's scope)
7649 //------------------------------------------------------------
7651 legend.dispatch.on('stateChange', function(newState) {
7652 for (var key in newState)
7653 state[key] = newState[key];
7654 dispatch.stateChange(state);
7658 controls.dispatch.on('legendClick', function(d,i) {
7659 if (!d.disabled) return;
7660 controlsData = controlsData.map(function(s) {
7668 multibar.stacked(false);
7671 multibar.stacked(true);
7675 state.stacked = multibar.stacked();
7676 dispatch.stateChange(state);
7677 stacked = multibar.stacked();
7682 dispatch.on('tooltipShow', function(e) {
7683 if (tooltips) showTooltip(e, that.parentNode);
7686 // Update chart from a state object passed to event handler
7687 dispatch.on('changeState', function(e) {
7689 if (typeof e.disabled !== 'undefined') {
7690 data.forEach(function(series,i) {
7691 series.disabled = e.disabled[i];
7694 state.disabled = e.disabled;
7697 if (typeof e.stacked !== 'undefined') {
7698 multibar.stacked(e.stacked);
7699 state.stacked = e.stacked;
7700 stacked = e.stacked;
7706 renderWatch.renderEnd('multibar horizontal chart immediate');
7710 //============================================================
7711 // Event Handling/Dispatching (out of chart's scope)
7712 //------------------------------------------------------------
7714 multibar.dispatch.on('elementMouseover.tooltip', function(e) {
7715 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7716 dispatch.tooltipShow(e);
7719 multibar.dispatch.on('elementMouseout.tooltip', function(e) {
7720 dispatch.tooltipHide(e);
7722 dispatch.on('tooltipHide', function() {
7723 if (tooltips) nv.tooltip.cleanup();
7726 //============================================================
7727 // Expose Public Variables
7728 //------------------------------------------------------------
7730 // expose chart's sub-components
7731 chart.dispatch = dispatch;
7732 chart.multibar = multibar;
7733 chart.legend = legend;
7734 chart.xAxis = xAxis;
7735 chart.yAxis = yAxis;
7736 chart.state = state;
7738 chart.options = nv.utils.optionsFunc.bind(chart);
7740 chart._options = Object.create({}, {
7741 // simple options, just get/set the necessary values
7742 width: {get: function(){return width;}, set: function(_){width=_;}},
7743 height: {get: function(){return height;}, set: function(_){height=_;}},
7744 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
7745 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
7746 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
7747 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
7748 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
7749 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
7750 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
7751 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
7753 // options that require extra logic in the setter
7754 margin: {get: function(){return margin;}, set: function(_){
7755 margin.top = _.top !== undefined ? _.top : margin.top;
7756 margin.right = _.right !== undefined ? _.right : margin.right;
7757 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7758 margin.left = _.left !== undefined ? _.left : margin.left;
7760 duration: {get: function(){return duration;}, set: function(_){
7762 renderWatch.reset(duration);
7763 multibar.duration(duration);
7764 xAxis.duration(duration);
7765 yAxis.duration(duration);
7767 color: {get: function(){return color;}, set: function(_){
7768 color = nv.utils.getColor(_);
7769 legend.color(color);
7773 nv.utils.inheritOptions(chart, multibar);
7774 nv.utils.initOptions(chart);
7778 nv.models.multiChart = function() {
7781 //============================================================
7782 // Public Variables with Default Settings
7783 //------------------------------------------------------------
7785 var margin = {top: 30, right: 20, bottom: 50, left: 60},
7786 color = nv.utils.defaultColor(),
7791 tooltip = function(key, x, y, e, graph) {
7792 return '<h3>' + key + '</h3>' +
7793 '<p>' + y + ' at ' + x + '</p>'
7797 noData = 'No Data Available.',
7800 getX = function(d) { return d.x },
7801 getY = function(d) { return d.y},
7802 interpolate = 'monotone'
7805 //============================================================
7806 // Private Variables
7807 //------------------------------------------------------------
7809 var x = d3.scale.linear(),
7810 yScale1 = d3.scale.linear(),
7811 yScale2 = d3.scale.linear(),
7813 lines1 = nv.models.line().yScale(yScale1),
7814 lines2 = nv.models.line().yScale(yScale2),
7816 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
7817 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
7819 stack1 = nv.models.stackedArea().yScale(yScale1),
7820 stack2 = nv.models.stackedArea().yScale(yScale2),
7822 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
7823 yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
7824 yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
7826 legend = nv.models.legend().height(30),
7827 dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
7829 var showTooltip = function(e, offsetElement) {
7830 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7831 top = e.pos[1] + ( offsetElement.offsetTop || 0),
7832 x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
7833 y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
7834 content = tooltip(e.series.key, x, y, e, chart);
7836 nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
7839 function chart(selection) {
7840 selection.each(function(data) {
7841 var container = d3.select(this),
7843 nv.utils.initSVG(container);
7845 chart.update = function() { container.transition().call(chart); };
7846 chart.container = this;
7848 var availableWidth = (width || parseInt(container.style('width')) || 960)
7849 - margin.left - margin.right,
7850 availableHeight = (height || parseInt(container.style('height')) || 400)
7851 - margin.top - margin.bottom;
7853 var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
7854 var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
7855 var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
7856 var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
7857 var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
7858 var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
7860 // Display noData message if there's nothing to show.
7861 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7862 var noDataText = container.selectAll('.nv-noData').data([noData]);
7864 noDataText.enter().append('text')
7865 .attr('class', 'nvd3 nv-noData')
7866 .attr('dy', '-.7em')
7867 .style('text-anchor', 'middle');
7870 .attr('x', margin.left + availableWidth / 2)
7871 .attr('y', margin.top + availableHeight / 2)
7872 .text(function(d) { return d });
7876 container.selectAll('.nv-noData').remove();
7879 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
7881 return d.values.map(function(d,i) {
7882 return { x: d.x, y: d.y }
7886 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
7888 return d.values.map(function(d,i) {
7889 return { x: d.x, y: d.y }
7893 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
7894 .range([0, availableWidth]);
7896 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
7897 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
7899 gEnter.append('g').attr('class', 'x axis');
7900 gEnter.append('g').attr('class', 'y1 axis');
7901 gEnter.append('g').attr('class', 'y2 axis');
7902 gEnter.append('g').attr('class', 'lines1Wrap');
7903 gEnter.append('g').attr('class', 'lines2Wrap');
7904 gEnter.append('g').attr('class', 'bars1Wrap');
7905 gEnter.append('g').attr('class', 'bars2Wrap');
7906 gEnter.append('g').attr('class', 'stack1Wrap');
7907 gEnter.append('g').attr('class', 'stack2Wrap');
7908 gEnter.append('g').attr('class', 'legendWrap');
7910 var g = wrap.select('g');
7912 var color_array = data.map(function(d,i) {
7913 return data[i].color || color(d, i);
7917 legend.color(color_array);
7918 legend.width( availableWidth / 2 );
7920 g.select('.legendWrap')
7921 .datum(data.map(function(series) {
7922 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
7923 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
7928 if ( margin.top != legend.height()) {
7929 margin.top = legend.height();
7930 availableHeight = (height || parseInt(container.style('height')) || 400)
7931 - margin.top - margin.bottom;
7934 g.select('.legendWrap')
7935 .attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
7939 .width(availableWidth)
7940 .height(availableHeight)
7941 .interpolate(interpolate)
7942 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
7944 .width(availableWidth)
7945 .height(availableHeight)
7946 .interpolate(interpolate)
7947 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
7949 .width(availableWidth)
7950 .height(availableHeight)
7951 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
7953 .width(availableWidth)
7954 .height(availableHeight)
7955 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
7957 .width(availableWidth)
7958 .height(availableHeight)
7959 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
7961 .width(availableWidth)
7962 .height(availableHeight)
7963 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
7965 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7967 var lines1Wrap = g.select('.lines1Wrap')
7969 dataLines1.filter(function(d){return !d.disabled})
7971 var bars1Wrap = g.select('.bars1Wrap')
7973 dataBars1.filter(function(d){return !d.disabled})
7975 var stack1Wrap = g.select('.stack1Wrap')
7977 dataStack1.filter(function(d){return !d.disabled})
7980 var lines2Wrap = g.select('.lines2Wrap')
7982 dataLines2.filter(function(d){return !d.disabled})
7984 var bars2Wrap = g.select('.bars2Wrap')
7986 dataBars2.filter(function(d){return !d.disabled})
7988 var stack2Wrap = g.select('.stack2Wrap')
7990 dataStack2.filter(function(d){return !d.disabled})
7993 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
7994 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
7995 }).concat([{x:0, y:0}]) : []
7996 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
7997 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
7998 }).concat([{x:0, y:0}]) : []
8000 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
8001 .range([0, availableHeight])
8003 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
8004 .range([0, availableHeight])
8006 lines1.yDomain(yScale1.domain())
8007 bars1.yDomain(yScale1.domain())
8008 stack1.yDomain(yScale1.domain())
8010 lines2.yDomain(yScale2.domain())
8011 bars2.yDomain(yScale2.domain())
8012 stack2.yDomain(yScale2.domain())
8014 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
8015 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
8017 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
8018 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
8020 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
8021 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
8024 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8025 .tickSize(-availableHeight, 0);
8028 .attr('transform', 'translate(0,' + availableHeight + ')');
8029 d3.transition(g.select('.x.axis'))
8033 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8034 .tickSize( -availableWidth, 0);
8037 d3.transition(g.select('.y1.axis'))
8041 .ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8042 .tickSize( -availableWidth, 0);
8044 d3.transition(g.select('.y2.axis'))
8047 g.select('.y1.axis')
8048 .classed('nv-disabled', series1.length ? false : true)
8049 .attr('transform', 'translate(' + x.range()[0] + ',0)');
8051 g.select('.y2.axis')
8052 .classed('nv-disabled', series2.length ? false : true)
8053 .attr('transform', 'translate(' + x.range()[1] + ',0)');
8055 legend.dispatch.on('stateChange', function(newState) {
8059 dispatch.on('tooltipShow', function(e) {
8060 if (tooltips) showTooltip(e, that.parentNode);
8068 //============================================================
8069 // Event Handling/Dispatching (out of chart's scope)
8070 //------------------------------------------------------------
8072 lines1.dispatch.on('elementMouseover.tooltip', function(e) {
8073 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8074 dispatch.tooltipShow(e);
8077 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8078 dispatch.tooltipHide(e);
8081 lines2.dispatch.on('elementMouseover.tooltip', function(e) {
8082 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8083 dispatch.tooltipShow(e);
8086 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8087 dispatch.tooltipHide(e);
8090 bars1.dispatch.on('elementMouseover.tooltip', function(e) {
8091 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8092 dispatch.tooltipShow(e);
8095 bars1.dispatch.on('elementMouseout.tooltip', function(e) {
8096 dispatch.tooltipHide(e);
8099 bars2.dispatch.on('elementMouseover.tooltip', function(e) {
8100 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8101 dispatch.tooltipShow(e);
8104 bars2.dispatch.on('elementMouseout.tooltip', function(e) {
8105 dispatch.tooltipHide(e);
8108 stack1.dispatch.on('tooltipShow', function(e) {
8109 //disable tooltips when value ~= 0
8110 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
8111 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
8112 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
8116 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8117 dispatch.tooltipShow(e);
8120 stack1.dispatch.on('tooltipHide', function(e) {
8121 dispatch.tooltipHide(e);
8124 stack2.dispatch.on('tooltipShow', function(e) {
8125 //disable tooltips when value ~= 0
8126 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
8127 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
8128 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
8132 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
8133 dispatch.tooltipShow(e);
8136 stack2.dispatch.on('tooltipHide', function(e) {
8137 dispatch.tooltipHide(e);
8140 lines1.dispatch.on('elementMouseover.tooltip', function(e) {
8141 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8142 dispatch.tooltipShow(e);
8145 lines1.dispatch.on('elementMouseout.tooltip', function(e) {
8146 dispatch.tooltipHide(e);
8149 lines2.dispatch.on('elementMouseover.tooltip', function(e) {
8150 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8151 dispatch.tooltipShow(e);
8154 lines2.dispatch.on('elementMouseout.tooltip', function(e) {
8155 dispatch.tooltipHide(e);
8158 dispatch.on('tooltipHide', function() {
8159 if (tooltips) nv.tooltip.cleanup();
8162 //============================================================
8163 // Global getters and setters
8164 //------------------------------------------------------------
8166 chart.dispatch = dispatch;
8167 chart.lines1 = lines1;
8168 chart.lines2 = lines2;
8169 chart.bars1 = bars1;
8170 chart.bars2 = bars2;
8171 chart.stack1 = stack1;
8172 chart.stack2 = stack2;
8173 chart.xAxis = xAxis;
8174 chart.yAxis1 = yAxis1;
8175 chart.yAxis2 = yAxis2;
8177 chart.options = nv.utils.optionsFunc.bind(chart);
8179 chart._options = Object.create({}, {
8180 // simple options, just get/set the necessary values
8181 width: {get: function(){return width;}, set: function(_){width=_;}},
8182 height: {get: function(){return height;}, set: function(_){height=_;}},
8183 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
8184 yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
8185 yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
8186 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
8187 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
8188 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
8189 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
8191 // options that require extra logic in the setter
8192 margin: {get: function(){return margin;}, set: function(_){
8193 margin.top = _.top !== undefined ? _.top : margin.top;
8194 margin.right = _.right !== undefined ? _.right : margin.right;
8195 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8196 margin.left = _.left !== undefined ? _.left : margin.left;
8198 color: {get: function(){return color;}, set: function(_){
8199 color = nv.utils.getColor(_);
8201 x: {get: function(){return getX;}, set: function(_){
8206 y: {get: function(){return getY;}, set: function(_){
8213 nv.utils.initOptions(chart);
8219 nv.models.ohlcBar = function() {
8222 //============================================================
8223 // Public Variables with Default Settings
8224 //------------------------------------------------------------
8226 var margin = {top: 0, right: 0, bottom: 0, left: 0}
8229 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8230 , x = d3.scale.linear()
8231 , y = d3.scale.linear()
8232 , getX = function(d) { return d.x }
8233 , getY = function(d) { return d.y }
8234 , getOpen = function(d) { return d.open }
8235 , getClose = function(d) { return d.close }
8236 , getHigh = function(d) { return d.high }
8237 , getLow = function(d) { return d.low }
8240 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
8242 , color = nv.utils.defaultColor()
8243 , interactive = false
8248 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8251 //============================================================
8252 // Private Variables
8253 //------------------------------------------------------------
8255 function chart(selection) {
8256 selection.each(function(data) {
8257 var container = d3.select(this);
8258 var availableWidth = (width || parseInt(container.style('width')) || 960)
8259 - margin.left - margin.right;
8260 var availableHeight = (height || parseInt(container.style('height')) || 400)
8261 - margin.top - margin.bottom;
8263 nv.utils.initSVG(container);
8266 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
8269 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
8271 x.range(xRange || [0, availableWidth]);
8273 y.domain(yDomain || [
8274 d3.min(data[0].values.map(getLow).concat(forceY)),
8275 d3.max(data[0].values.map(getHigh).concat(forceY))
8277 ).range(yRange || [availableHeight, 0]);
8279 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
8280 if (x.domain()[0] === x.domain()[1])
8282 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
8285 if (y.domain()[0] === y.domain()[1])
8287 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
8290 // Setup containers and skeleton of chart
8291 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
8292 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
8293 var defsEnter = wrapEnter.append('defs');
8294 var gEnter = wrapEnter.append('g');
8295 var g = wrap.select('g');
8297 gEnter.append('g').attr('class', 'nv-ticks');
8299 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8302 .on('click', function(d,i) {
8303 dispatch.chartClick({
8311 defsEnter.append('clipPath')
8312 .attr('id', 'nv-chart-clip-path-' + id)
8315 wrap.select('#nv-chart-clip-path-' + id + ' rect')
8316 .attr('width', availableWidth)
8317 .attr('height', availableHeight);
8319 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
8321 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
8322 .data(function(d) { return d });
8323 ticks.exit().remove();
8325 var ticksEnter = ticks.enter().append('path')
8326 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
8327 .attr('d', function(d,i) {
8328 var w = (availableWidth / data[0].values.length) * .9;
8337 + (y(getLow(d,i)) - y(getOpen(d,i)))
8347 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
8348 .attr('fill', function(d,i) { return color[0]; })
8349 .attr('stroke', function(d,i) { return color[0]; })
8351 .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
8352 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
8354 // the bar colors are controlled by CSS currently
8355 ticks.attr('class', function(d,i,j) {
8356 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
8359 d3.transition(ticks)
8360 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
8361 .attr('d', function(d,i) {
8362 var w = (availableWidth / data[0].values.length) * .9;
8388 //Create methods to allow outside functions to highlight a specific bar.
8389 chart.highlightPoint = function(pointIndex, isHoverOver) {
8390 chart.clearHighlights();
8391 d3.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
8392 .classed("hover", isHoverOver)
8396 chart.clearHighlights = function() {
8397 d3.select(".nv-ohlcBar .nv-tick.hover")
8398 .classed("hover", false)
8402 //============================================================
8403 // Expose Public Variables
8404 //------------------------------------------------------------
8406 chart.dispatch = dispatch;
8407 chart.options = nv.utils.optionsFunc.bind(chart);
8409 chart._options = Object.create({}, {
8410 // simple options, just get/set the necessary values
8411 width: {get: function(){return width;}, set: function(_){width=_;}},
8412 height: {get: function(){return height;}, set: function(_){height=_;}},
8413 xScale: {get: function(){return x;}, set: function(_){x=_;}},
8414 yScale: {get: function(){return y;}, set: function(_){y=_;}},
8415 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
8416 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
8417 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
8418 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
8419 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
8420 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
8421 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
8422 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
8423 id: {get: function(){return id;}, set: function(_){id=_;}},
8424 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
8426 x: {get: function(){return getX;}, set: function(_){getX=_;}},
8427 y: {get: function(){return getY;}, set: function(_){getY=_;}},
8428 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
8429 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
8430 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
8431 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
8433 // options that require extra logic in the setter
8434 margin: {get: function(){return margin;}, set: function(_){
8435 margin.top = _.top != undefined ? _.top : margin.top;
8436 margin.right = _.right != undefined ? _.right : margin.right;
8437 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
8438 margin.left = _.left != undefined ? _.left : margin.left;
8440 color: {get: function(){return color;}, set: function(_){
8441 color = nv.utils.getColor(_);
8445 nv.utils.initOptions(chart);
8449 // Code adapted from Jason Davies' "Parallel Coordinates"
8450 // http://bl.ocks.org/jasondavies/1341281
8452 nv.models.parallelCoordinates = function() {
8455 //============================================================
8456 // Public Variables with Default Settings
8457 //------------------------------------------------------------
8459 var margin = {top: 30, right: 10, bottom: 10, left: 10}
8462 , x = d3.scale.ordinal()
8465 , color = nv.utils.defaultColor()
8468 , dispatch = d3.dispatch('brush')
8471 //============================================================
8472 // Private Variables
8473 //------------------------------------------------------------
8475 function chart(selection) {
8476 selection.each(function(data) {
8477 var container = d3.select(this);
8478 var availableWidth = (width || parseInt(container.style('width')) || 960)
8479 - margin.left - margin.right;
8480 var availableHeight = (height || parseInt(container.style('height')) || 400)
8481 - margin.top - margin.bottom;
8483 nv.utils.initSVG(container);
8485 active = data; //set all active before first brush call
8487 //This is a placeholder until this chart is made resizeable
8488 chart.update = function() { };
8491 x.rangePoints([0, availableWidth], 1).domain(dimensions);
8493 // Extract the list of dimensions and create a scale for each.
8494 dimensions.forEach(function(d) {
8495 y[d] = d3.scale.linear()
8496 .domain(d3.extent(data, function(p) { return +p[d]; }))
8497 .range([availableHeight, 0]);
8499 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
8504 // Setup containers and skeleton of chart
8505 var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
8506 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
8507 var gEnter = wrapEnter.append('g');
8508 var g = wrap.select('g');
8510 gEnter.append('g').attr('class', 'nv-parallelCoordinatesWrap');
8511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8513 var line = d3.svg.line(),
8514 axis = d3.svg.axis().orient('left'),
8518 // Add grey background lines for context.
8519 background = gEnter.append('g')
8520 .attr('class', 'background')
8523 .enter().append('path')
8527 // Add blue foreground lines for focus.
8528 foreground = gEnter.append('g')
8529 .attr('class', 'foreground')
8532 .enter().append('path')
8534 .attr('stroke', color)
8537 // Add a group element for each dimension.
8538 var dimension = g.selectAll('.dimension')
8540 .enter().append('g')
8541 .attr('class', 'dimension')
8542 .attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
8544 // Add an axis and title.
8545 dimension.append('g')
8546 .attr('class', 'axis')
8547 .each(function(d) { d3.select(this).call(axis.scale(y[d])); })
8549 .attr('text-anchor', 'middle')
8553 // Add and store a brush for each axis.
8554 dimension.append('g')
8555 .attr('class', 'brush')
8556 .each(function(d) { d3.select(this).call(y[d].brush); })
8561 // Returns the path for a given data point.
8563 return line(dimensions.map(function(p) { return [x(p), y[p](d[p])]; }));
8566 // Handles a brush event, toggling the display of foreground lines.
8568 var actives = dimensions.filter(function(p) { return !y[p].brush.empty(); }),
8569 extents = actives.map(function(p) { return y[p].brush.extent(); });
8571 filters = []; //erase current filters
8572 actives.forEach(function(d,i) {
8579 active = []; //erase current active list
8580 foreground.style('display', function(d) {
8581 var isActive = actives.every(function(p, i) {
8582 return extents[i][0] <= d[p] && d[p] <= extents[i][1];
8584 if (isActive) active.push(d);
8585 return isActive ? null : 'none';
8598 //============================================================
8599 // Expose Public Variables
8600 //------------------------------------------------------------
8602 chart.dispatch = dispatch;
8603 chart.options = nv.utils.optionsFunc.bind(chart);
8605 chart._options = Object.create({}, {
8606 // simple options, just get/set the necessary values
8607 width: {get: function(){return width;}, set: function(_){width=_;}},
8608 height: {get: function(){return height;}, set: function(_){height=_;}},
8609 dimensions: {get: function(){return dimensions;}, set: function(_){dimensions=_;}},
8611 // options that require extra logic in the setter
8612 margin: {get: function(){return margin;}, set: function(_){
8613 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8614 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8615 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8616 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8618 color: {get: function(){return color;}, set: function(_){
8619 color = nv.utils.getColor(_);
8623 nv.utils.initOptions(chart);
8626 nv.models.pie = function() {
8629 //============================================================
8630 // Public Variables with Default Settings
8631 //------------------------------------------------------------
8633 var margin = {top: 0, right: 0, bottom: 0, left: 0}
8636 , getX = function(d) { return d.x }
8637 , getY = function(d) { return d.y }
8638 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8639 , color = nv.utils.defaultColor()
8640 , valueFormat = d3.format(',.2f')
8641 , labelFormat = d3.format('%')
8643 , pieLabelsOutside = true
8644 , donutLabelsOutside = false
8646 , labelThreshold = .02 //if slice percentage is under this, don't show label
8649 , growOnHover = true
8651 , labelSunbeamLayout = false
8652 , startAngle = false
8658 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
8662 //============================================================
8664 //------------------------------------------------------------
8666 var renderWatch = nv.utils.renderWatch(dispatch);
8668 function chart(selection) {
8669 renderWatch.reset();
8670 selection.each(function(data) {
8671 var availableWidth = width - margin.left - margin.right
8672 ,availableHeight = height - margin.top - margin.bottom
8673 ,radius = Math.min(availableWidth, availableHeight) / 2
8674 ,arcRadius = radius-(radius / 5)
8675 ,container = d3.select(this)
8677 nv.utils.initSVG(container);
8679 // Setup containers and skeleton of chart
8680 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
8681 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
8682 var gEnter = wrapEnter.append('g');
8683 var g = wrap.select('g');
8684 var g_pie = gEnter.append('g').attr('class', 'nv-pie');
8685 gEnter.append('g').attr('class', 'nv-pieLabels');
8687 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8688 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
8689 g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
8692 container.on('click', function(d,i) {
8693 dispatch.chartClick({
8702 var arc = d3.svg.arc().outerRadius(arcRadius);
8703 var arcOver = d3.svg.arc().outerRadius(arcRadius + 5);
8706 arc.startAngle(startAngle);
8707 arcOver.startAngle(startAngle);
8710 arc.endAngle(endAngle);
8711 arcOver.endAngle(endAngle);
8714 arc.innerRadius(radius * donutRatio);
8715 arcOver.innerRadius(radius * donutRatio);
8718 // Setup the Pie chart and choose the data element
8719 var pie = d3.layout.pie()
8721 .value(function(d) { return d.disabled ? 0 : getY(d) });
8723 // padAngle added in d3 3.5
8724 if (pie.padAngle && padAngle) {
8725 pie.padAngle(padAngle);
8728 if (arc.cornerRadius && cornerRadius) {
8729 arc.cornerRadius(cornerRadius);
8730 arcOver.cornerRadius(cornerRadius);
8733 // if title is specified and donut, put it in the middle
8734 if (donut && title) {
8735 var title_g = g_pie.append('g').attr('class', 'nv-pie');
8737 title_g.append("text")
8738 .style("text-anchor", "middle")
8739 .attr('class', 'nv-pie-title')
8740 .text(function (d) {
8743 .attr("dy", "0.35em") // trick to vertically center text
8744 .attr('transform', function(d, i) {
8745 return 'translate(0, '+ titleOffset + ')';
8749 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
8750 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
8752 slices.exit().remove();
8753 pieLabels.exit().remove();
8755 var ae = slices.enter().append('g')
8756 ae.attr('class', 'nv-slice')
8757 ae.on('mouseover', function(d,i){
8758 d3.select(this).classed('hover', true);
8760 d3.select(this).select("path").transition()
8762 .attr("d", arcOver);
8764 dispatch.elementMouseover({
8765 label: getX(d.data),
8766 value: getY(d.data),
8769 pos: [d3.event.pageX, d3.event.pageY],
8771 color: d3.select(this).style("fill")
8774 ae.on('mouseout', function(d,i){
8775 d3.select(this).classed('hover', false);
8777 d3.select(this).select("path").transition()
8781 dispatch.elementMouseout({
8782 label: getX(d.data),
8783 value: getY(d.data),
8789 ae.on('click', function(d,i) {
8790 dispatch.elementClick({
8791 label: getX(d.data),
8792 value: getY(d.data),
8798 d3.event.stopPropagation();
8800 ae.on('dblclick', function(d,i) {
8801 dispatch.elementDblClick({
8802 label: getX(d.data),
8803 value: getY(d.data),
8809 d3.event.stopPropagation();
8812 slices.attr('fill', function(d,i) { return color(d, i); })
8813 slices.attr('stroke', function(d,i) { return color(d, i); });
8815 var paths = ae.append('path').each(function(d) {
8819 slices.select('path')
8822 .attrTween('d', arcTween);
8825 // This does the normal label
8826 var labelsArc = d3.svg.arc().innerRadius(0);
8828 if (pieLabelsOutside){
8829 var labelsArc = arc;
8832 if (donutLabelsOutside) {
8833 labelsArc = d3.svg.arc().outerRadius(arc.outerRadius());
8836 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
8837 var group = d3.select(this);
8839 group.attr('transform', function(d) {
8840 if (labelSunbeamLayout) {
8841 d.outerRadius = arcRadius + 10; // Set Outer Coordinate
8842 d.innerRadius = arcRadius + 15; // Set Inner Coordinate
8843 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
8844 if ((d.startAngle+d.endAngle)/2 < Math.PI) {
8849 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
8851 d.outerRadius = radius + 10; // Set Outer Coordinate
8852 d.innerRadius = radius + 15; // Set Inner Coordinate
8853 return 'translate(' + labelsArc.centroid(d) + ')'
8857 group.append('rect')
8858 .style('stroke', '#fff')
8859 .style('fill', '#fff')
8863 group.append('text')
8864 .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
8865 .style('fill', '#000')
8869 var labelLocationHash = {};
8872 var createHashKey = function(coordinates) {
8873 return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
8876 pieLabels.watchTransition(renderWatch,'pie labels').attr('transform', function(d) {
8877 if (labelSunbeamLayout) {
8878 d.outerRadius = arcRadius + 10; // Set Outer Coordinate
8879 d.innerRadius = arcRadius + 15; // Set Inner Coordinate
8880 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
8881 if ((d.startAngle+d.endAngle)/2 < Math.PI) {
8886 return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
8888 d.outerRadius = radius + 10; // Set Outer Coordinate
8889 d.innerRadius = radius + 15; // Set Inner Coordinate
8892 Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
8893 Each label location is hashed, and if a hash collision occurs, we assume an overlap.
8894 Adjust the label's y-position to remove the overlap.
8896 var center = labelsArc.centroid(d);
8898 var hashKey = createHashKey(center);
8899 if (labelLocationHash[hashKey]) {
8900 center[1] -= avgHeight;
8902 labelLocationHash[createHashKey(center)] = true;
8904 return 'translate(' + center + ')'
8908 pieLabels.select(".nv-label text")
8909 .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
8910 .text(function(d, i) {
8911 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
8913 "key" : getX(d.data),
8914 "value": getY(d.data),
8915 "percent": labelFormat(percent)
8917 return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
8923 // Computes the angle of an arc, converting from radians to degrees.
8925 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
8926 return a > 90 ? a - 180 : a;
8929 function arcTween(a) {
8930 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
8931 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
8932 if (!donut) a.innerRadius = 0;
8933 var i = d3.interpolate(this._current, a);
8934 this._current = i(0);
8935 return function(t) {
8941 renderWatch.renderEnd('pie immediate');
8945 //============================================================
8946 // Expose Public Variables
8947 //------------------------------------------------------------
8949 chart.dispatch = dispatch;
8950 chart.options = nv.utils.optionsFunc.bind(chart);
8952 chart._options = Object.create({}, {
8953 // simple options, just get/set the necessary values
8954 width: {get: function(){return width;}, set: function(_){width=_;}},
8955 height: {get: function(){return height;}, set: function(_){height=_;}},
8956 showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
8957 title: {get: function(){return title;}, set: function(_){title=_;}},
8958 titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
8959 labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
8960 labelFormat: {get: function(){return labelFormat;}, set: function(_){labelFormat=_;}},
8961 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
8962 x: {get: function(){return getX;}, set: function(_){getX=_;}},
8963 id: {get: function(){return id;}, set: function(_){id=_;}},
8964 endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
8965 startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
8966 padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
8967 cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
8968 donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
8969 pieLabelsOutside: {get: function(){return pieLabelsOutside;}, set: function(_){pieLabelsOutside=_;}},
8970 donutLabelsOutside: {get: function(){return donutLabelsOutside;}, set: function(_){donutLabelsOutside=_;}},
8971 labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
8972 donut: {get: function(){return donut;}, set: function(_){donut=_;}},
8973 growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
8975 // options that require extra logic in the setter
8976 margin: {get: function(){return margin;}, set: function(_){
8977 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8978 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8979 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8980 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8982 y: {get: function(){return getY;}, set: function(_){
8985 color: {get: function(){return color;}, set: function(_){
8986 color=nv.utils.getColor(_);
8988 labelType: {get: function(){return labelType;}, set: function(_){
8989 labelType= _ || 'key';
8993 nv.utils.initOptions(chart);
8996 nv.models.pieChart = function() {
8999 //============================================================
9000 // Public Variables with Default Settings
9001 //------------------------------------------------------------
9003 var pie = nv.models.pie();
9004 var legend = nv.models.legend();
9006 var margin = {top: 30, right: 20, bottom: 20, left: 20}
9010 , color = nv.utils.defaultColor()
9012 , tooltip = function(key, y, e, graph) {
9013 return '<h3 style="background-color: '
9014 + e.color + '">' + key + '</h3>'
9015 + '<p>' + y + '</p>';
9017 , state = nv.utils.state()
9018 , defaultState = null
9019 , noData = "No Data Available."
9021 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
9024 //============================================================
9025 // Private Variables
9026 //------------------------------------------------------------
9028 var showTooltip = function(e, offsetElement) {
9029 var tooltipLabel = pie.x()(e.point);
9030 var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
9031 top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
9032 y = pie.valueFormat()(pie.y()(e.point)),
9033 content = tooltip(tooltipLabel, y, e, chart)
9035 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
9038 var renderWatch = nv.utils.renderWatch(dispatch);
9040 var stateGetter = function(data) {
9043 active: data.map(function(d) { return !d.disabled })
9048 var stateSetter = function(data) {
9049 return function(state) {
9050 if (state.active !== undefined) {
9051 data.forEach(function (series, i) {
9052 series.disabled = !state.active[i];
9058 //============================================================
9060 //------------------------------------------------------------
9062 function chart(selection) {
9063 renderWatch.reset();
9064 renderWatch.models(pie);
9066 selection.each(function(data) {
9067 var container = d3.select(this);
9068 nv.utils.initSVG(container);
9071 var availableWidth = (width || parseInt(container.style('width'), 10) || 960)
9072 - margin.left - margin.right,
9073 availableHeight = (height || parseInt(container.style('height'), 10) || 400)
9074 - margin.top - margin.bottom
9077 chart.update = function() { container.transition().call(chart); };
9078 chart.container = this;
9080 state.setter(stateSetter(data), chart.update)
9081 .getter(stateGetter(data))
9084 //set state.disabled
9085 state.disabled = data.map(function(d) { return !!d.disabled });
9087 if (!defaultState) {
9090 for (key in state) {
9091 if (state[key] instanceof Array)
9092 defaultState[key] = state[key].slice(0);
9094 defaultState[key] = state[key];
9098 // Display No Data message if there's nothing to show.
9099 if (!data || !data.length) {
9100 var noDataText = container.selectAll('.nv-noData').data([noData]);
9102 noDataText.enter().append('text')
9103 .attr('class', 'nvd3 nv-noData')
9104 .attr('dy', '-.7em')
9105 .style('text-anchor', 'middle');
9108 .attr('x', margin.left + availableWidth / 2)
9109 .attr('y', margin.top + availableHeight / 2)
9110 .text(function(d) { return d });
9114 container.selectAll('.nv-noData').remove();
9117 // Setup containers and skeleton of chart
9118 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
9119 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
9120 var g = wrap.select('g');
9122 gEnter.append('g').attr('class', 'nv-pieWrap');
9123 gEnter.append('g').attr('class', 'nv-legendWrap');
9127 legend.width( availableWidth ).key(pie.x());
9129 wrap.select('.nv-legendWrap')
9133 if ( margin.top != legend.height()) {
9134 margin.top = legend.height();
9135 availableHeight = (height || parseInt(container.style('height')) || 400)
9136 - margin.top - margin.bottom;
9139 wrap.select('.nv-legendWrap')
9140 .attr('transform', 'translate(0,' + (-margin.top) +')');
9142 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9144 // Main Chart Component(s)
9145 pie.width(availableWidth).height(availableHeight);
9146 var pieWrap = g.select('.nv-pieWrap').datum([data]);
9147 d3.transition(pieWrap).call(pie);
9149 // Event Handling/Dispatching (in chart's scope)
9150 legend.dispatch.on('stateChange', function(newState) {
9151 for (var key in newState) {
9152 state[key] = newState[key];
9154 dispatch.stateChange(state);
9158 pie.dispatch.on('elementMouseout.tooltip', function(e) {
9159 dispatch.tooltipHide(e);
9162 // Update chart from a state object passed to event handler
9163 dispatch.on('changeState', function(e) {
9164 if (typeof e.disabled !== 'undefined') {
9165 data.forEach(function(series,i) {
9166 series.disabled = e.disabled[i];
9168 state.disabled = e.disabled;
9175 renderWatch.renderEnd('pieChart immediate');
9179 //============================================================
9180 // Event Handling/Dispatching (out of chart's scope)
9181 //------------------------------------------------------------
9183 pie.dispatch.on('elementMouseover.tooltip', function(e) {
9184 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9185 dispatch.tooltipShow(e);
9188 dispatch.on('tooltipShow', function(e) {
9189 if (tooltips) showTooltip(e);
9192 dispatch.on('tooltipHide', function() {
9193 if (tooltips) nv.tooltip.cleanup();
9196 //============================================================
9197 // Expose Public Variables
9198 //------------------------------------------------------------
9200 // expose chart's sub-components
9201 chart.legend = legend;
9202 chart.dispatch = dispatch;
9204 chart.options = nv.utils.optionsFunc.bind(chart);
9206 // use Object get/set functionality to map between vars and chart functions
9207 chart._options = Object.create({}, {
9208 // simple options, just get/set the necessary values
9209 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
9210 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
9211 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
9212 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
9213 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
9214 // options that require extra logic in the setter
9215 color: {get: function(){return color;}, set: function(_){
9217 legend.color(color);
9220 duration: {get: function(){return duration;}, set: function(_){
9222 renderWatch.reset(duration);
9225 nv.utils.inheritOptions(chart, pie);
9226 nv.utils.initOptions(chart);
9230 nv.models.scatter = function() {
9233 //============================================================
9234 // Public Variables with Default Settings
9235 //------------------------------------------------------------
9237 var margin = {top: 0, right: 0, bottom: 0, left: 0}
9240 , color = nv.utils.defaultColor() // chooses color
9241 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
9242 , x = d3.scale.linear()
9243 , y = d3.scale.linear()
9244 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
9245 , getX = function(d) { return d.x } // accessor to get the x value
9246 , getY = function(d) { return d.y } // accessor to get the y value
9247 , getSize = function(d) { return d.size || 1} // accessor to get the point size
9248 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
9249 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
9250 , forceY = [] // List of numbers to Force into the Y scale
9251 , forceSize = [] // List of numbers to Force into the Size scale
9252 , interactive = true // If true, plots a voronoi overlay for advanced point intersection
9253 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
9254 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9255 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
9256 , clipEdge = false // if true, masks points within x and y scale
9257 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
9258 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
9259 , xDomain = null // Override x domain (skips the calculation from data)
9260 , yDomain = null // Override y domain
9261 , xRange = null // Override x range
9262 , yRange = null // Override y range
9263 , sizeDomain = null // Override point size domain
9265 , singlePoint = false
9266 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
9272 //============================================================
9273 // Private Variables
9274 //------------------------------------------------------------
9276 var x0, y0, z0 // used to store previous scales
9278 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
9279 , renderWatch = nv.utils.renderWatch(dispatch, duration)
9282 function chart(selection) {
9283 renderWatch.reset();
9284 selection.each(function(data) {
9285 var container = d3.select(this);
9286 var availableWidth = (width || parseInt(container.style('width')) || 960)
9287 - margin.left - margin.right;
9288 var availableHeight = (height || parseInt(container.style('height')) || 400)
9289 - margin.top - margin.bottom;
9291 nv.utils.initSVG(container);
9293 //add series index to each data point for reference
9294 data.forEach(function(series, i) {
9295 series.values.forEach(function(point) {
9301 // remap and flatten the data for use in calculating the scales' domains
9302 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
9304 data.map(function(d) {
9305 return d.values.map(function(d,i) {
9306 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
9311 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
9313 if (padData && data[0])
9314 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
9315 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
9317 x.range(xRange || [0, availableWidth]);
9319 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
9320 .range(yRange || [availableHeight, 0]);
9322 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
9323 .range(sizeRange || [16, 256]);
9325 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9326 if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
9327 if (x.domain()[0] === x.domain()[1])
9329 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9332 if (y.domain()[0] === y.domain()[1])
9334 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
9337 if ( isNaN(x.domain()[0])) {
9341 if ( isNaN(y.domain()[0])) {
9349 // Setup containers and skeleton of chart
9350 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
9351 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
9352 var defsEnter = wrapEnter.append('defs');
9353 var gEnter = wrapEnter.append('g');
9354 var g = wrap.select('g');
9356 gEnter.append('g').attr('class', 'nv-groups');
9357 gEnter.append('g').attr('class', 'nv-point-paths');
9359 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9361 defsEnter.append('clipPath')
9362 .attr('id', 'nv-edge-clip-' + id)
9365 wrap.select('#nv-edge-clip-' + id + ' rect')
9366 .attr('width', availableWidth)
9367 .attr('height', (availableHeight > 0) ? availableHeight : 0);
9369 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
9371 function updateInteractiveLayer() {
9373 if (!interactive) return false;
9377 var vertices = d3.merge(data.map(function(group, groupIndex) {
9379 .map(function(point, pointIndex) {
9380 // *Adding noise to make duplicates very unlikely
9381 // *Injecting series and point index for reference
9382 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
9384 var pX = getX(point,pointIndex);
9385 var pY = getY(point,pointIndex);
9387 return [x(pX)+ Math.random() * 1e-7,
9388 y(pY)+ Math.random() * 1e-7,
9390 pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
9392 .filter(function(pointArray, pointIndex) {
9393 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
9398 //inject series and point index for reference into voronoi
9399 if (useVoronoi === true) {
9401 if(vertices.length < 3) {
9402 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
9403 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
9404 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
9405 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
9406 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
9409 // keep voronoi sections from going more than 10 outside of graph
9410 // to avoid overlap with other things like legend etc
9411 var bounds = d3.geom.polygon([
9414 [width + 10,height + 10],
9418 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
9420 'data': bounds.clip(d),
9421 'series': vertices[i][2],
9422 'point': vertices[i][3]
9426 // nuke all voronoi paths on reload and recreate them
9427 wrap.select('.nv-point-paths').selectAll('path').remove();
9428 var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
9430 .enter().append("svg:path")
9431 .attr("d", function(d) {
9432 if (!d || !d.data || d.data.length === 0)
9435 return "M" + d.data.join(",") + "Z";
9437 .attr("id", function(d,i) {
9438 return "nv-path-"+i; })
9439 .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
9441 // chain these to above to see the voronoi elements (good for debugging)
9442 //.style("fill", d3.rgb(230, 230, 230))
9443 //.style('fill-opacity', 0.4)
9444 //.style('stroke-opacity', 1)
9445 //.style("stroke", d3.rgb(200,200,200));
9448 // voronoi sections are already set to clip,
9449 // just create the circles with the IDs they expect
9450 var clips = wrap.append("svg:g").attr("id", "nv-point-clips");
9451 clips.selectAll("clipPath")
9453 .enter().append("svg:clipPath")
9454 .attr("id", function(d, i) { return "nv-clip-"+i;})
9455 .append("svg:circle")
9456 .attr('cx', function(d) { return d[0]; })
9457 .attr('cy', function(d) { return d[1]; })
9458 .attr('r', clipRadius);
9461 var mouseEventCallback = function(d,mDispatch) {
9462 if (needsUpdate) return 0;
9463 var series = data[d.series];
9464 if (typeof series === 'undefined') return;
9465 var point = series.values[d.point];
9470 pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
9471 seriesIndex: d.series,
9477 .on('click', function(d) {
9478 mouseEventCallback(d, dispatch.elementClick);
9480 .on('dblclick', function(d) {
9481 mouseEventCallback(d, dispatch.elementDblClick);
9483 .on('mouseover', function(d) {
9484 mouseEventCallback(d, dispatch.elementMouseover);
9486 .on('mouseout', function(d, i) {
9487 mouseEventCallback(d, dispatch.elementMouseout);
9492 // bring data in form needed for click handlers
9493 var dataWithPoints = vertices.map(function(d, i) {
9496 'series': vertices[i][2],
9497 'point': vertices[i][3]
9502 // add event handlers to points instead voronoi paths
9503 wrap.select('.nv-groups').selectAll('.nv-group')
9504 .selectAll('.nv-point')
9505 //.data(dataWithPoints)
9506 //.style('pointer-events', 'auto') // recativate events, disabled by css
9507 .on('click', function(d,i) {
9508 //nv.log('test', d, i);
9509 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
9510 var series = data[d.series],
9511 point = series.values[i];
9513 dispatch.elementClick({
9516 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
9517 seriesIndex: d.series,
9521 .on('mouseover', function(d,i) {
9522 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
9523 var series = data[d.series],
9524 point = series.values[i];
9526 dispatch.elementMouseover({
9529 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
9530 seriesIndex: d.series,
9534 .on('mouseout', function(d,i) {
9535 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
9536 var series = data[d.series],
9537 point = series.values[i];
9539 dispatch.elementMouseout({
9542 seriesIndex: d.series,
9548 needsUpdate = false;
9552 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
9553 .data(function(d) { return d }, function(d) { return d.key });
9554 groups.enter().append('g')
9555 .style('stroke-opacity', 1e-6)
9556 .style('fill-opacity', 1e-6);
9560 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
9561 .classed('hover', function(d) { return d.hover });
9562 groups.watchTransition(renderWatch, 'scatter: groups')
9563 .style('fill', function(d,i) { return color(d, i) })
9564 .style('stroke', function(d,i) { return color(d, i) })
9565 .style('stroke-opacity', 1)
9566 .style('fill-opacity', .5);
9568 // create the points
9569 var points = groups.selectAll('path.nv-point')
9570 .data(function(d) { return d.values });
9571 points.enter().append('path')
9572 .style('fill', function (d,i) { return d.color })
9573 .style('stroke', function (d,i) { return d.color })
9574 .attr('transform', function(d,i) {
9575 return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
9580 .size(function(d,i) { return z(getSize(d,i)) })
9582 points.exit().remove();
9583 groups.exit().selectAll('path.nv-point')
9584 .watchTransition(renderWatch, 'scatter exit')
9585 .attr('transform', function(d,i) {
9586 return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
9589 points.each(function(d,i) {
9591 .classed('nv-point', true)
9592 .classed('nv-point-' + i, true)
9593 .classed('hover',false)
9597 .watchTransition(renderWatch, 'scatter points')
9598 .attr('transform', function(d,i) {
9599 //nv.log(d,i,getX(d,i), x(getX(d,i)));
9600 return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
9605 .size(function(d,i) { return z(getSize(d,i)) })
9608 // Delay updating the invisible interactive layer for smoother animation
9609 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
9610 timeoutID = setTimeout(updateInteractiveLayer, 300);
9611 //updateInteractiveLayer();
9613 //store old scales for use in transitions on update
9619 renderWatch.renderEnd('scatter immediate');
9623 //============================================================
9624 // Expose Public Variables
9625 //------------------------------------------------------------
9627 chart.dispatch = dispatch;
9628 chart.options = nv.utils.optionsFunc.bind(chart);
9630 // utility function calls provided by this chart
9631 chart._calls = new function() {
9632 this.clearHighlights = function () {
9633 d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover", false);
9636 this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
9637 d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
9638 .classed("hover", isHoverOver);
9642 // trigger calls from events too
9643 dispatch.on('elementMouseover.point', function(d) {
9644 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
9647 dispatch.on('elementMouseout.point', function(d) {
9648 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
9651 chart._options = Object.create({}, {
9652 // simple options, just get/set the necessary values
9653 width: {get: function(){return width;}, set: function(_){width=_;}},
9654 height: {get: function(){return height;}, set: function(_){height=_;}},
9655 xScale: {get: function(){return x;}, set: function(_){x=_;}},
9656 yScale: {get: function(){return y;}, set: function(_){y=_;}},
9657 pointScale: {get: function(){return z;}, set: function(_){z=_;}},
9658 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
9659 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
9660 pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
9661 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
9662 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
9663 pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
9664 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
9665 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
9666 forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
9667 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
9668 pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
9669 padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
9670 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
9671 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
9672 clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
9673 clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
9674 id: {get: function(){return id;}, set: function(_){id=_;}},
9677 // simple functor options
9678 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
9679 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
9680 pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
9681 pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
9683 // options that require extra logic in the setter
9684 margin: {get: function(){return margin;}, set: function(_){
9685 margin.top = _.top !== undefined ? _.top : margin.top;
9686 margin.right = _.right !== undefined ? _.right : margin.right;
9687 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
9688 margin.left = _.left !== undefined ? _.left : margin.left;
9690 duration: {get: function(){return duration;}, set: function(_){
9692 renderWatch.reset(duration);
9694 color: {get: function(){return color;}, set: function(_){
9695 color = nv.utils.getColor(_);
9697 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
9699 if (useVoronoi === false) {
9700 clipVoronoi = false;
9705 nv.utils.initOptions(chart);
9709 nv.models.scatterChart = function() {
9712 //============================================================
9713 // Public Variables with Default Settings
9714 //------------------------------------------------------------
9716 var scatter = nv.models.scatter()
9717 , xAxis = nv.models.axis()
9718 , yAxis = nv.models.axis()
9719 , legend = nv.models.legend()
9720 , distX = nv.models.distribution()
9721 , distY = nv.models.distribution()
9724 var margin = {top: 30, right: 20, bottom: 50, left: 75}
9727 , color = nv.utils.defaultColor()
9728 , x = scatter.xScale()
9729 , y = scatter.yScale()
9735 , rightAlignYAxis = false
9737 , tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
9738 , tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
9739 , tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>'
9740 + '<p>' + date + '</p>' }
9741 , state = nv.utils.state()
9742 , defaultState = null
9743 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
9744 , noData = "No Data Available."
9757 .orient((rightAlignYAxis) ? 'right' : 'left')
9767 //============================================================
9768 // Private Variables
9769 //------------------------------------------------------------
9772 , renderWatch = nv.utils.renderWatch(dispatch, duration);
9774 var showTooltip = function(e, offsetElement) {
9775 //TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?
9776 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9777 top = e.pos[1] + ( offsetElement.offsetTop || 0),
9778 leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9779 topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
9780 leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
9781 topY = e.pos[1] + ( offsetElement.offsetTop || 0),
9782 xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
9783 yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
9785 if( tooltipX != null )
9786 nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
9787 if( tooltipY != null )
9788 nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
9789 if( tooltip != null )
9790 nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
9793 var stateGetter = function(data) {
9796 active: data.map(function(d) { return !d.disabled })
9801 var stateSetter = function(data) {
9802 return function(state) {
9803 if (state.active !== undefined)
9804 data.forEach(function(series,i) {
9805 series.disabled = !state.active[i];
9810 function chart(selection) {
9811 renderWatch.reset();
9812 renderWatch.models(scatter);
9813 if (showXAxis) renderWatch.models(xAxis);
9814 if (showYAxis) renderWatch.models(yAxis);
9815 if (showDistX) renderWatch.models(distX);
9816 if (showDistY) renderWatch.models(distY);
9818 selection.each(function(data) {
9819 var container = d3.select(this),
9821 nv.utils.initSVG(container);
9823 var availableWidth = (width || parseInt(container.style('width')) || 960)
9824 - margin.left - margin.right,
9825 availableHeight = (height || parseInt(container.style('height')) || 400)
9826 - margin.top - margin.bottom;
9828 chart.update = function() {
9830 container.call(chart);
9832 container.transition().duration(duration).call(chart);
9834 chart.container = this;
9837 .setter(stateSetter(data), chart.update)
9838 .getter(stateGetter(data))
9841 // DEPRECATED set state.disableddisabled
9842 state.disabled = data.map(function(d) { return !!d.disabled });
9844 if (!defaultState) {
9847 for (key in state) {
9848 if (state[key] instanceof Array)
9849 defaultState[key] = state[key].slice(0);
9851 defaultState[key] = state[key];
9855 // Display noData message if there's nothing to show.
9856 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9857 var noDataText = container.selectAll('.nv-noData').data([noData]);
9859 noDataText.enter().append('text')
9860 .attr('class', 'nvd3 nv-noData')
9861 .attr('dy', '-.7em')
9862 .style('text-anchor', 'middle');
9865 .attr('x', margin.left + availableWidth / 2)
9866 .attr('y', margin.top + availableHeight / 2)
9867 .text(function(d) { return d });
9869 renderWatch.renderEnd('scatter immediate');
9873 container.selectAll('.nv-noData').remove();
9877 x = scatter.xScale();
9878 y = scatter.yScale();
9880 // Setup containers and skeleton of chart
9881 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
9882 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
9883 var gEnter = wrapEnter.append('g');
9884 var g = wrap.select('g');
9886 // background for pointer events
9887 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
9889 gEnter.append('g').attr('class', 'nv-x nv-axis');
9890 gEnter.append('g').attr('class', 'nv-y nv-axis');
9891 gEnter.append('g').attr('class', 'nv-scatterWrap');
9892 gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
9893 gEnter.append('g').attr('class', 'nv-distWrap');
9894 gEnter.append('g').attr('class', 'nv-legendWrap');
9896 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9898 if (rightAlignYAxis) {
9899 g.select(".nv-y.nv-axis")
9900 .attr("transform", "translate(" + availableWidth + ",0)");
9905 legend.width( availableWidth / 2 );
9907 wrap.select('.nv-legendWrap')
9911 if ( margin.top != legend.height()) {
9912 margin.top = legend.height();
9913 availableHeight = (height || parseInt(container.style('height')) || 400)
9914 - margin.top - margin.bottom;
9917 wrap.select('.nv-legendWrap')
9918 .attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
9921 // Main Chart Component(s)
9923 .width(availableWidth)
9924 .height(availableHeight)
9925 .color(data.map(function(d,i) {
9926 return d.color || color(d, i);
9927 }).filter(function(d,i) { return !data[i].disabled }));
9929 wrap.select('.nv-scatterWrap')
9930 .datum(data.filter(function(d) { return !d.disabled }))
9934 wrap.select('.nv-regressionLinesWrap')
9935 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
9937 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
9938 .data(function (d) {
9942 regWrap.enter().append('g').attr('class', 'nv-regLines');
9944 var regLine = regWrap.selectAll('.nv-regLine')
9945 .data(function (d) {
9950 .append('line').attr('class', 'nv-regLine')
9951 .style('stroke-opacity', 0);
9953 // don't add lines unless we have slope and intercept to use
9954 regLine.filter(function(d) {
9955 return d.intercept && d.slope;
9957 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
9958 .attr('x1', x.range()[0])
9959 .attr('x2', x.range()[1])
9960 .attr('y1', function (d, i) {
9961 return y(x.domain()[0] * d.slope + d.intercept)
9963 .attr('y2', function (d, i) {
9964 return y(x.domain()[1] * d.slope + d.intercept)
9966 .style('stroke', function (d, i, j) {
9969 .style('stroke-opacity', function (d, i) {
9970 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
9977 .ticks( xAxis.ticks() ? xAxis.ticks() : nv.utils.calcTicksX(availableWidth/100, data) )
9978 .tickSize( -availableHeight , 0);
9980 g.select('.nv-x.nv-axis')
9981 .attr('transform', 'translate(0,' + y.range()[0] + ')')
9988 .ticks( yAxis.ticks() ? yAxis.ticks() : nv.utils.calcTicksY(availableHeight/36, data) )
9989 .tickSize( -availableWidth, 0);
9991 g.select('.nv-y.nv-axis')
9998 .getData(scatter.x())
10000 .width(availableWidth)
10001 .color(data.map(function(d,i) {
10002 return d.color || color(d, i);
10003 }).filter(function(d,i) { return !data[i].disabled }));
10004 gEnter.select('.nv-distWrap').append('g')
10005 .attr('class', 'nv-distributionX');
10006 g.select('.nv-distributionX')
10007 .attr('transform', 'translate(0,' + y.range()[0] + ')')
10008 .datum(data.filter(function(d) { return !d.disabled }))
10014 .getData(scatter.y())
10016 .width(availableHeight)
10017 .color(data.map(function(d,i) {
10018 return d.color || color(d, i);
10019 }).filter(function(d,i) { return !data[i].disabled }));
10020 gEnter.select('.nv-distWrap').append('g')
10021 .attr('class', 'nv-distributionY');
10022 g.select('.nv-distributionY')
10023 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
10024 .datum(data.filter(function(d) { return !d.disabled }))
10028 //============================================================
10029 // Event Handling/Dispatching (in chart's scope)
10030 //------------------------------------------------------------
10032 legend.dispatch.on('stateChange', function(newState) {
10033 for (var key in newState)
10034 state[key] = newState[key];
10035 dispatch.stateChange(state);
10040 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
10041 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
10042 .attr('y1', e.pos[1] - availableHeight);
10043 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
10044 .attr('x2', e.pos[0] + distX.size());
10046 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10047 dispatch.tooltipShow(e);
10050 dispatch.on('tooltipShow', function(e) {
10051 if (tooltips) showTooltip(e, that.parentNode);
10054 // Update chart from a state object passed to event handler
10055 dispatch.on('changeState', function(e) {
10057 if (typeof e.disabled !== 'undefined') {
10058 data.forEach(function(series,i) {
10059 series.disabled = e.disabled[i];
10062 state.disabled = e.disabled;
10068 //store old scales for use in transitions on update
10074 renderWatch.renderEnd('scatter with line immediate');
10078 //============================================================
10079 // Event Handling/Dispatching (out of chart's scope)
10080 //------------------------------------------------------------
10082 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
10083 dispatch.tooltipHide(e);
10085 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
10087 d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
10088 .attr('x2', distY.size());
10090 dispatch.on('tooltipHide', function() {
10091 if (tooltips) nv.tooltip.cleanup();
10094 //============================================================
10095 // Expose Public Variables
10096 //------------------------------------------------------------
10098 // expose chart's sub-components
10099 chart.dispatch = dispatch;
10100 chart.scatter = scatter;
10101 chart.legend = legend;
10102 chart.xAxis = xAxis;
10103 chart.yAxis = yAxis;
10104 chart.distX = distX;
10105 chart.distY = distY;
10107 chart._options = Object.create({}, {
10108 // simple options, just get/set the necessary values
10109 width: {get: function(){return width;}, set: function(_){width=_;}},
10110 height: {get: function(){return height;}, set: function(_){height=_;}},
10111 showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
10112 showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
10113 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
10114 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
10115 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
10116 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
10117 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
10118 tooltipXContent: {get: function(){return tooltipX;}, set: function(_){tooltipX=_;}},
10119 tooltipYContent: {get: function(){return tooltipY;}, set: function(_){tooltipY=_;}},
10120 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
10121 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
10122 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
10124 // options that require extra logic in the setter
10125 margin: {get: function(){return margin;}, set: function(_){
10126 margin.top = _.top !== undefined ? _.top : margin.top;
10127 margin.right = _.right !== undefined ? _.right : margin.right;
10128 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10129 margin.left = _.left !== undefined ? _.left : margin.left;
10131 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
10132 rightAlignYAxis = _;
10133 yAxis.orient( (_) ? 'right' : 'left');
10135 color: {get: function(){return color;}, set: function(_){
10136 color = nv.utils.getColor(_);
10137 legend.color(color);
10138 distX.color(color);
10139 distY.color(color);
10143 nv.utils.inheritOptions(chart, scatter);
10144 nv.utils.initOptions(chart);
10148 nv.models.sparkline = function() {
10151 //============================================================
10152 // Public Variables with Default Settings
10153 //------------------------------------------------------------
10155 var margin = {top: 2, right: 0, bottom: 2, left: 0}
10159 , x = d3.scale.linear()
10160 , y = d3.scale.linear()
10161 , getX = function(d) { return d.x }
10162 , getY = function(d) { return d.y }
10163 , color = nv.utils.getColor(['#000'])
10170 function chart(selection) {
10171 selection.each(function(data) {
10172 var availableWidth = width - margin.left - margin.right,
10173 availableHeight = height - margin.top - margin.bottom,
10174 container = d3.select(this);
10175 nv.utils.initSVG(container);
10178 x .domain(xDomain || d3.extent(data, getX ))
10179 .range(xRange || [0, availableWidth]);
10181 y .domain(yDomain || d3.extent(data, getY ))
10182 .range(yRange || [availableHeight, 0]);
10184 // Setup containers and skeleton of chart
10185 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
10186 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
10187 var gEnter = wrapEnter.append('g');
10188 var g = wrap.select('g');
10190 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
10192 var paths = wrap.selectAll('path')
10193 .data(function(d) { return [d] });
10194 paths.enter().append('path');
10195 paths.exit().remove();
10197 .style('stroke', function(d,i) { return d.color || color(d, i) })
10198 .attr('d', d3.svg.line()
10199 .x(function(d,i) { return x(getX(d,i)) })
10200 .y(function(d,i) { return y(getY(d,i)) })
10203 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
10204 var points = wrap.selectAll('circle.nv-point')
10205 .data(function(data) {
10206 var yValues = data.map(function(d, i) { return getY(d,i); });
10207 function pointIndex(index) {
10209 var result = data[index];
10210 result.pointIndex = index;
10216 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
10217 minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
10218 currentPoint = pointIndex(yValues.length - 1);
10219 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
10221 points.enter().append('circle');
10222 points.exit().remove();
10224 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
10225 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
10227 .attr('class', function(d,i) {
10228 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
10229 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
10236 //============================================================
10237 // Expose Public Variables
10238 //------------------------------------------------------------
10240 chart.options = nv.utils.optionsFunc.bind(chart);
10242 chart._options = Object.create({}, {
10243 // simple options, just get/set the necessary values
10244 width: {get: function(){return width;}, set: function(_){width=_;}},
10245 height: {get: function(){return height;}, set: function(_){height=_;}},
10246 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
10247 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
10248 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
10249 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
10250 xScale: {get: function(){return x;}, set: function(_){x=_;}},
10251 yScale: {get: function(){return y;}, set: function(_){y=_;}},
10252 animate: {get: function(){return animate;}, set: function(_){animate=_;}},
10255 x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
10256 y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
10258 // options that require extra logic in the setter
10259 margin: {get: function(){return margin;}, set: function(_){
10260 margin.top = _.top !== undefined ? _.top : margin.top;
10261 margin.right = _.right !== undefined ? _.right : margin.right;
10262 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10263 margin.left = _.left !== undefined ? _.left : margin.left;
10265 color: {get: function(){return color;}, set: function(_){
10266 color = nv.utils.getColor(_);
10270 nv.utils.initOptions(chart);
10274 nv.models.sparklinePlus = function() {
10277 //============================================================
10278 // Public Variables with Default Settings
10279 //------------------------------------------------------------
10281 var sparkline = nv.models.sparkline();
10283 var margin = {top: 15, right: 100, bottom: 10, left: 50}
10290 , xTickFormat = d3.format(',r')
10291 , yTickFormat = d3.format(',.2f')
10293 , alignValue = true
10294 , rightAlignValue = false
10295 , noData = "No Data Available."
10298 function chart(selection) {
10299 selection.each(function(data) {
10300 var container = d3.select(this);
10301 nv.utils.initSVG(container);
10303 var availableWidth = (width || parseInt(container.style('width')) || 960)
10304 - margin.left - margin.right,
10305 availableHeight = (height || parseInt(container.style('height')) || 400)
10306 - margin.top - margin.bottom;
10308 chart.update = function() { chart(selection) };
10309 chart.container = this;
10311 // Display No Data message if there's nothing to show.
10312 if (!data || !data.length) {
10313 var noDataText = container.selectAll('.nv-noData').data([noData]);
10315 noDataText.enter().append('text')
10316 .attr('class', 'nvd3 nv-noData')
10317 .attr('dy', '-.7em')
10318 .style('text-anchor', 'middle');
10321 .attr('x', margin.left + availableWidth / 2)
10322 .attr('y', margin.top + availableHeight / 2)
10323 .text(function(d) { return d });
10327 container.selectAll('.nv-noData').remove();
10330 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
10333 x = sparkline.xScale();
10334 y = sparkline.yScale();
10336 // Setup containers and skeleton of chart
10337 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
10338 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
10339 var gEnter = wrapEnter.append('g');
10340 var g = wrap.select('g');
10342 gEnter.append('g').attr('class', 'nv-sparklineWrap');
10343 gEnter.append('g').attr('class', 'nv-valueWrap');
10344 gEnter.append('g').attr('class', 'nv-hoverArea');
10346 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10348 // Main Chart Component(s)
10349 var sparklineWrap = g.select('.nv-sparklineWrap');
10351 sparkline.width(availableWidth).height(availableHeight);
10352 sparklineWrap.call(sparkline);
10354 var valueWrap = g.select('.nv-valueWrap');
10355 var value = valueWrap.selectAll('.nv-currentValue')
10356 .data([currentValue]);
10358 value.enter().append('text').attr('class', 'nv-currentValue')
10359 .attr('dx', rightAlignValue ? -8 : 8)
10360 .attr('dy', '.9em')
10361 .style('text-anchor', rightAlignValue ? 'end' : 'start');
10364 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
10365 .attr('y', alignValue ? function(d) { return y(d) } : 0)
10366 .style('fill', sparkline.color()(data[data.length-1], data.length-1))
10367 .text(yTickFormat(currentValue));
10369 gEnter.select('.nv-hoverArea').append('rect')
10370 .on('mousemove', sparklineHover)
10371 .on('click', function() { paused = !paused })
10372 .on('mouseout', function() { index = []; updateValueLine(); });
10374 g.select('.nv-hoverArea rect')
10375 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
10376 .attr('width', availableWidth + margin.left + margin.right)
10377 .attr('height', availableHeight + margin.top);
10379 //index is currently global (within the chart), may or may not keep it that way
10380 function updateValueLine() {
10381 if (paused) return;
10383 var hoverValue = g.selectAll('.nv-hoverValue').data(index)
10385 var hoverEnter = hoverValue.enter()
10386 .append('g').attr('class', 'nv-hoverValue')
10387 .style('stroke-opacity', 0)
10388 .style('fill-opacity', 0);
10391 .transition().duration(250)
10392 .style('stroke-opacity', 0)
10393 .style('fill-opacity', 0)
10397 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
10398 .transition().duration(250)
10399 .style('stroke-opacity', 1)
10400 .style('fill-opacity', 1);
10402 if (!index.length) return;
10404 hoverEnter.append('line')
10406 .attr('y1', -margin.top)
10408 .attr('y2', availableHeight);
10410 hoverEnter.append('text').attr('class', 'nv-xValue')
10412 .attr('y', -margin.top)
10413 .attr('text-anchor', 'end')
10414 .attr('dy', '.9em')
10416 g.select('.nv-hoverValue .nv-xValue')
10417 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
10419 hoverEnter.append('text').attr('class', 'nv-yValue')
10421 .attr('y', -margin.top)
10422 .attr('text-anchor', 'start')
10423 .attr('dy', '.9em')
10425 g.select('.nv-hoverValue .nv-yValue')
10426 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
10429 function sparklineHover() {
10430 if (paused) return;
10432 var pos = d3.mouse(this)[0] - margin.left;
10434 function getClosestIndex(data, x) {
10435 var distance = Math.abs(sparkline.x()(data[0], 0) - x);
10436 var closestIndex = 0;
10437 for (var i = 0; i < data.length; i++){
10438 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
10439 distance = Math.abs(sparkline.x()(data[i], i) - x);
10443 return closestIndex;
10446 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
10455 //============================================================
10456 // Expose Public Variables
10457 //------------------------------------------------------------
10459 // expose chart's sub-components
10460 chart.sparkline = sparkline;
10462 chart.options = nv.utils.optionsFunc.bind(chart);
10464 chart._options = Object.create({}, {
10465 // simple options, just get/set the necessary values
10466 width: {get: function(){return width;}, set: function(_){width=_;}},
10467 height: {get: function(){return height;}, set: function(_){height=_;}},
10468 xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
10469 yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
10470 showValue: {get: function(){return showValue;}, set: function(_){showValue=_;}},
10471 alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
10472 rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
10473 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
10475 // options that require extra logic in the setter
10476 margin: {get: function(){return margin;}, set: function(_){
10477 margin.top = _.top !== undefined ? _.top : margin.top;
10478 margin.right = _.right !== undefined ? _.right : margin.right;
10479 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10480 margin.left = _.left !== undefined ? _.left : margin.left;
10484 nv.utils.inheritOptions(chart, sparkline);
10485 nv.utils.initOptions(chart);
10490 nv.models.stackedArea = function() {
10493 //============================================================
10494 // Public Variables with Default Settings
10495 //------------------------------------------------------------
10497 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10500 , color = nv.utils.defaultColor() // a function that computes the color
10501 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
10502 , getX = function(d) { return d.x } // accessor to get the x value from a data point
10503 , getY = function(d) { return d.y } // accessor to get the y value from a data point
10506 , order = 'default'
10507 , interpolate = 'linear' // controls the line interpolation
10508 , clipEdge = false // if true, masks lines within x and y scale
10509 , x //can be accessed via chart.xScale()
10510 , y //can be accessed via chart.yScale()
10511 , scatter = nv.models.scatter()
10513 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout','renderEnd')
10516 // scatter is interactive by default, but this chart isn't so must disable
10517 scatter.interactive(false);
10520 .pointSize(2.2) // default size
10521 .pointDomain([2.2, 2.2]) // all the same size by default
10524 /************************************
10526 * 'wiggle' (stream)
10528 * 'expand' (normalize to 100%)
10529 * 'silhouette' (simple centered)
10532 * 'inside-out' (stream)
10533 * 'default' (input order)
10534 ************************************/
10536 var renderWatch = nv.utils.renderWatch(dispatch, duration);
10538 function chart(selection) {
10539 renderWatch.reset();
10540 renderWatch.models(scatter);
10541 selection.each(function(data) {
10542 var availableWidth = width - margin.left - margin.right,
10543 availableHeight = height - margin.top - margin.bottom,
10544 container = d3.select(this);
10545 nv.utils.initSVG(container);
10548 x = scatter.xScale();
10549 y = scatter.yScale();
10551 var dataRaw = data;
10552 // Injecting point index into each point because d3.layout.stack().out does not give index
10553 data.forEach(function(aseries, i) {
10554 aseries.seriesIndex = i;
10555 aseries.values = aseries.values.map(function(d, j) {
10562 var dataFiltered = data.filter(function(series) {
10563 return !series.disabled;
10566 data = d3.layout.stack()
10569 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
10572 .out(function(d, y0, y) {
10573 var yHeight = (getY(d) === 0) ? 0 : y;
10581 // Setup containers and skeleton of chart
10582 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
10583 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
10584 var defsEnter = wrapEnter.append('defs');
10585 var gEnter = wrapEnter.append('g');
10586 var g = wrap.select('g');
10588 gEnter.append('g').attr('class', 'nv-areaWrap');
10589 gEnter.append('g').attr('class', 'nv-scatterWrap');
10591 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10594 .width(availableWidth)
10595 .height(availableHeight)
10597 .y(function(d) { return d.display.y + d.display.y0 })
10599 .color(data.map(function(d,i) {
10600 return d.color || color(d, d.seriesIndex);
10603 var scatterWrap = g.select('.nv-scatterWrap')
10606 scatterWrap.call(scatter);
10608 defsEnter.append('clipPath')
10609 .attr('id', 'nv-edge-clip-' + id)
10612 wrap.select('#nv-edge-clip-' + id + ' rect')
10613 .attr('width', availableWidth)
10614 .attr('height', availableHeight);
10616 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10618 var area = d3.svg.area()
10619 .x(function(d,i) { return x(getX(d,i)) })
10621 return y(d.display.y0)
10624 return y(d.display.y + d.display.y0)
10626 .interpolate(interpolate);
10628 var zeroArea = d3.svg.area()
10629 .x(function(d,i) { return x(getX(d,i)) })
10630 .y0(function(d) { return y(d.display.y0) })
10631 .y1(function(d) { return y(d.display.y0) });
10633 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
10634 .data(function(d) { return d });
10636 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
10637 .attr('d', function(d,i){
10638 return zeroArea(d.values, d.seriesIndex);
10640 .on('mouseover', function(d,i) {
10641 d3.select(this).classed('hover', true);
10642 dispatch.areaMouseover({
10645 pos: [d3.event.pageX, d3.event.pageY],
10646 seriesIndex: d.seriesIndex
10649 .on('mouseout', function(d,i) {
10650 d3.select(this).classed('hover', false);
10651 dispatch.areaMouseout({
10654 pos: [d3.event.pageX, d3.event.pageY],
10655 seriesIndex: d.seriesIndex
10658 .on('click', function(d,i) {
10659 d3.select(this).classed('hover', false);
10660 dispatch.areaClick({
10663 pos: [d3.event.pageX, d3.event.pageY],
10664 seriesIndex: d.seriesIndex
10668 path.exit().remove();
10669 path.style('fill', function(d,i){
10670 return d.color || color(d, d.seriesIndex)
10672 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
10673 path.watchTransition(renderWatch,'stackedArea path')
10674 .attr('d', function(d,i) {
10675 return area(d.values,i)
10678 //============================================================
10679 // Event Handling/Dispatching (in chart's scope)
10680 //------------------------------------------------------------
10682 scatter.dispatch.on('elementMouseover.area', function(e) {
10683 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
10685 scatter.dispatch.on('elementMouseout.area', function(e) {
10686 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
10689 //Special offset functions
10690 chart.d3_stackedOffset_stackPercent = function(stackData) {
10691 var n = stackData.length, //How many series
10692 m = stackData[0].length, //how many points per series
10699 for (j = 0; j < m; ++j) { //Looping through all points
10700 for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through series'
10701 o += getY(dataRaw[i].values[j]); //total value of all points at a certian point in time.
10704 if (o) for (i = 0; i < n; i++) {
10705 stackData[i][j][1] /= o;
10707 for (i = 0; i < n; i++) {
10708 stackData[i][j][1] = k;
10712 for (j = 0; j < m; ++j) y0[j] = 0;
10718 renderWatch.renderEnd('stackedArea immediate');
10723 //============================================================
10724 // Event Handling/Dispatching (out of chart's scope)
10725 //------------------------------------------------------------
10727 scatter.dispatch.on('elementClick.area', function(e) {
10728 dispatch.areaClick(e);
10730 scatter.dispatch.on('elementMouseover.tooltip', function(e) {
10731 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
10732 dispatch.tooltipShow(e);
10734 scatter.dispatch.on('elementMouseout.tooltip', function(e) {
10735 dispatch.tooltipHide(e);
10738 //============================================================
10739 // Global getters and setters
10740 //------------------------------------------------------------
10742 chart.dispatch = dispatch;
10743 chart.scatter = scatter;
10745 chart.interpolate = function(_) {
10746 if (!arguments.length) return interpolate;
10751 chart.duration = function(_) {
10752 if (!arguments.length) return duration;
10754 renderWatch.reset(duration);
10755 scatter.duration(duration);
10759 chart.dispatch = dispatch;
10760 chart.options = nv.utils.optionsFunc.bind(chart);
10762 chart._options = Object.create({}, {
10763 // simple options, just get/set the necessary values
10764 width: {get: function(){return width;}, set: function(_){width=_;}},
10765 height: {get: function(){return height;}, set: function(_){height=_;}},
10766 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
10767 offset: {get: function(){return offset;}, set: function(_){offset=_;}},
10768 order: {get: function(){return order;}, set: function(_){order=_;}},
10769 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
10771 // simple functor options
10772 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
10773 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
10775 // options that require extra logic in the setter
10776 margin: {get: function(){return margin;}, set: function(_){
10777 margin.top = _.top !== undefined ? _.top : margin.top;
10778 margin.right = _.right !== undefined ? _.right : margin.right;
10779 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10780 margin.left = _.left !== undefined ? _.left : margin.left;
10782 color: {get: function(){return color;}, set: function(_){
10783 color = nv.utils.getColor(_);
10785 style: {get: function(){return style;}, set: function(_){
10789 chart.offset('zero');
10790 chart.order('default');
10793 chart.offset('wiggle');
10794 chart.order('inside-out');
10796 case 'stream-center':
10797 chart.offset('silhouette');
10798 chart.order('inside-out');
10801 chart.offset('expand');
10802 chart.order('default');
10804 case 'stack_percent':
10805 chart.offset(chart.d3_stackedOffset_stackPercent);
10806 chart.order('default');
10810 duration: {get: function(){return duration;}, set: function(_){
10812 renderWatch.reset(duration);
10813 scatter.duration(duration);
10817 nv.utils.inheritOptions(chart, scatter);
10818 nv.utils.initOptions(chart);
10823 nv.models.stackedAreaChart = function() {
10826 //============================================================
10827 // Public Variables with Default Settings
10828 //------------------------------------------------------------
10830 var stacked = nv.models.stackedArea()
10831 , xAxis = nv.models.axis()
10832 , yAxis = nv.models.axis()
10833 , legend = nv.models.legend()
10834 , controls = nv.models.legend()
10835 , interactiveLayer = nv.interactiveGuideline()
10838 var margin = {top: 30, right: 25, bottom: 50, left: 60}
10841 , color = nv.utils.defaultColor()
10842 , showControls = true
10843 , showLegend = true
10846 , rightAlignYAxis = false
10847 , useInteractiveGuideline = false
10849 , tooltip = function(key, x, y, e, graph) {
10850 return '<h3>' + key + '</h3>' +
10851 '<p>' + y + ' on ' + x + '</p>'
10853 , x //can be accessed via chart.xScale()
10854 , y //can be accessed via chart.yScale()
10855 , yAxisTickFormat = d3.format(',.2f')
10856 , state = nv.utils.state()
10857 , defaultState = null
10858 , noData = 'No Data Available.'
10859 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
10860 , controlWidth = 250
10861 , cData = ['Stacked','Stream','Expanded']
10862 , controlLabels = {}
10866 state.style = stacked.style();
10867 xAxis.orient('bottom').tickPadding(7);
10868 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
10870 controls.updateState(false);
10872 //============================================================
10873 // Private Variables
10874 //------------------------------------------------------------
10876 var renderWatch = nv.utils.renderWatch(dispatch);
10877 var style = stacked.style();
10879 var showTooltip = function(e, offsetElement) {
10880 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
10881 top = e.pos[1] + ( offsetElement.offsetTop || 0),
10882 x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
10883 y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
10884 content = tooltip(e.series.key, x, y, e, chart);
10886 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
10889 var stateGetter = function(data) {
10892 active: data.map(function(d) { return !d.disabled }),
10893 style: stacked.style()
10898 var stateSetter = function(data) {
10899 return function(state) {
10900 if (state.style !== undefined)
10901 style = state.style;
10902 if (state.active !== undefined)
10903 data.forEach(function(series,i) {
10904 series.disabled = !state.active[i];
10909 function chart(selection) {
10910 renderWatch.reset();
10911 renderWatch.models(stacked);
10912 if (showXAxis) renderWatch.models(xAxis);
10913 if (showYAxis) renderWatch.models(yAxis);
10915 selection.each(function(data) {
10916 var container = d3.select(this),
10918 nv.utils.initSVG(container);
10920 var availableWidth = (width || parseInt(container.style('width')) || 960)
10921 - margin.left - margin.right,
10922 availableHeight = (height || parseInt(container.style('height')) || 400)
10923 - margin.top - margin.bottom;
10925 chart.update = function() { container.transition().duration(duration).call(chart); };
10926 chart.container = this;
10929 .setter(stateSetter(data), chart.update)
10930 .getter(stateGetter(data))
10933 // DEPRECATED set state.disabled
10934 state.disabled = data.map(function(d) { return !!d.disabled });
10936 if (!defaultState) {
10939 for (key in state) {
10940 if (state[key] instanceof Array)
10941 defaultState[key] = state[key].slice(0);
10943 defaultState[key] = state[key];
10947 // Display No Data message if there's nothing to show.
10948 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
10949 var noDataText = container.selectAll('.nv-noData').data([noData]);
10951 noDataText.enter().append('text')
10952 .attr('class', 'nvd3 nv-noData')
10953 .attr('dy', '-.7em')
10954 .style('text-anchor', 'middle');
10957 .attr('x', margin.left + availableWidth / 2)
10958 .attr('y', margin.top + availableHeight / 2)
10959 .text(function(d) { return d });
10963 container.selectAll('.nv-noData').remove();
10967 x = stacked.xScale();
10968 y = stacked.yScale();
10970 // Setup containers and skeleton of chart
10971 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
10972 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
10973 var g = wrap.select('g');
10975 gEnter.append("rect").style("opacity",0);
10976 gEnter.append('g').attr('class', 'nv-x nv-axis');
10977 gEnter.append('g').attr('class', 'nv-y nv-axis');
10978 gEnter.append('g').attr('class', 'nv-stackedWrap');
10979 gEnter.append('g').attr('class', 'nv-legendWrap');
10980 gEnter.append('g').attr('class', 'nv-controlsWrap');
10981 gEnter.append('g').attr('class', 'nv-interactive');
10983 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
10987 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
10989 legend.width(legendWidth);
10990 g.select('.nv-legendWrap').datum(data).call(legend);
10992 if ( margin.top != legend.height()) {
10993 margin.top = legend.height();
10994 availableHeight = (height || parseInt(container.style('height')) || 400)
10995 - margin.top - margin.bottom;
10998 g.select('.nv-legendWrap')
10999 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
11003 if (showControls) {
11004 var controlsData = [
11006 key: controlLabels.stacked || 'Stacked',
11007 metaKey: 'Stacked',
11008 disabled: stacked.style() != 'stack',
11012 key: controlLabels.stream || 'Stream',
11014 disabled: stacked.style() != 'stream',
11018 key: controlLabels.expanded || 'Expanded',
11019 metaKey: 'Expanded',
11020 disabled: stacked.style() != 'expand',
11024 key: controlLabels.stack_percent || 'Stack %',
11025 metaKey: 'Stack_Percent',
11026 disabled: stacked.style() != 'stack_percent',
11027 style: 'stack_percent'
11031 controlWidth = (cData.length/3) * 260;
11032 controlsData = controlsData.filter(function(d) {
11033 return cData.indexOf(d.metaKey) !== -1;
11037 .width( controlWidth )
11038 .color(['#444', '#444', '#444']);
11040 g.select('.nv-controlsWrap')
11041 .datum(controlsData)
11044 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
11045 margin.top = Math.max(controls.height(), legend.height());
11046 availableHeight = (height || parseInt(container.style('height')) || 400)
11047 - margin.top - margin.bottom;
11050 g.select('.nv-controlsWrap')
11051 .attr('transform', 'translate(0,' + (-margin.top) +')');
11054 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11056 if (rightAlignYAxis) {
11057 g.select(".nv-y.nv-axis")
11058 .attr("transform", "translate(" + availableWidth + ",0)");
11061 //Set up interactive layer
11062 if (useInteractiveGuideline) {
11064 .width(availableWidth)
11065 .height(availableHeight)
11066 .margin({left: margin.left, top: margin.top})
11067 .svgContainer(container)
11069 wrap.select(".nv-interactive").call(interactiveLayer);
11073 .width(availableWidth)
11074 .height(availableHeight);
11076 var stackedWrap = g.select('.nv-stackedWrap')
11079 stackedWrap.transition().call(stacked);
11084 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
11085 .tickSize( -availableHeight, 0);
11087 g.select('.nv-x.nv-axis')
11088 .attr('transform', 'translate(0,' + availableHeight + ')');
11090 g.select('.nv-x.nv-axis')
11091 .transition().duration(0)
11097 .ticks(stacked.offset() == 'wiggle' ? 0 : nv.utils.calcTicksY(availableHeight/36, data) )
11098 .tickSize(-availableWidth, 0)
11099 .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
11100 ? d3.format('%') : yAxisTickFormat);
11102 g.select('.nv-y.nv-axis')
11103 .transition().duration(0)
11107 //============================================================
11108 // Event Handling/Dispatching (in chart's scope)
11109 //------------------------------------------------------------
11111 stacked.dispatch.on('areaClick.toggle', function(e) {
11112 if (data.filter(function(d) { return !d.disabled }).length === 1)
11113 data.forEach(function(d) {
11114 d.disabled = false;
11117 data.forEach(function(d,i) {
11118 d.disabled = (i != e.seriesIndex);
11121 state.disabled = data.map(function(d) { return !!d.disabled });
11122 dispatch.stateChange(state);
11127 legend.dispatch.on('stateChange', function(newState) {
11128 for (var key in newState)
11129 state[key] = newState[key];
11130 dispatch.stateChange(state);
11134 controls.dispatch.on('legendClick', function(d,i) {
11135 if (!d.disabled) return;
11137 controlsData = controlsData.map(function(s) {
11141 d.disabled = false;
11143 stacked.style(d.style);
11146 state.style = stacked.style();
11147 dispatch.stateChange(state);
11152 interactiveLayer.dispatch.on('elementMousemove', function(e) {
11153 stacked.clearHighlights();
11154 var singlePoint, pointIndex, pointXLocation, allData = [];
11156 .filter(function(series, i) {
11157 series.seriesIndex = i;
11158 return !series.disabled;
11160 .forEach(function(series,i) {
11161 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
11162 stacked.highlightPoint(i, pointIndex, true);
11163 var point = series.values[pointIndex];
11164 if (typeof point === 'undefined') return;
11165 if (typeof singlePoint === 'undefined') singlePoint = point;
11166 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
11168 //If we are in 'expand' mode, use the stacked percent value instead of raw value.
11169 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
11172 value: tooltipValue,
11173 color: color(series,series.seriesIndex),
11174 stackedValue: point.display
11180 //Highlight the tooltip entry based on which stack the mouse is closest to.
11181 if (allData.length > 2) {
11182 var yValue = chart.yScale().invert(e.mouseY);
11183 var yDistMax = Infinity, indexToHighlight = null;
11184 allData.forEach(function(series,i) {
11186 //To handle situation where the stacked area chart is negative, we need to use absolute values
11187 //when checking if the mouse Y value is within the stack area.
11188 yValue = Math.abs(yValue);
11189 var stackedY0 = Math.abs(series.stackedValue.y0);
11190 var stackedY = Math.abs(series.stackedValue.y);
11191 if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
11193 indexToHighlight = i;
11197 if (indexToHighlight != null)
11198 allData[indexToHighlight].highlight = true;
11201 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
11203 //If we are in 'expand' mode, force the format to be a percentage.
11204 var valueFormatter = (stacked.style() == 'expand') ?
11205 function(d,i) {return d3.format(".1%")(d);} :
11206 function(d,i) {return yAxis.tickFormat()(d); };
11207 interactiveLayer.tooltip
11208 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
11209 .chartContainer(that.parentNode)
11211 .valueFormatter(valueFormatter)
11219 interactiveLayer.renderGuideLine(pointXLocation);
11223 interactiveLayer.dispatch.on("elementMouseout",function(e) {
11224 dispatch.tooltipHide();
11225 stacked.clearHighlights();
11229 dispatch.on('tooltipShow', function(e) {
11230 if (tooltips) showTooltip(e, that.parentNode);
11233 // Update chart from a state object passed to event handler
11234 dispatch.on('changeState', function(e) {
11236 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
11237 data.forEach(function(series,i) {
11238 series.disabled = e.disabled[i];
11241 state.disabled = e.disabled;
11244 if (typeof e.style !== 'undefined') {
11245 stacked.style(e.style);
11254 renderWatch.renderEnd('stacked Area chart immediate');
11258 //============================================================
11259 // Event Handling/Dispatching (out of chart's scope)
11260 //------------------------------------------------------------
11262 stacked.dispatch.on('tooltipShow', function(e) {
11263 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11264 dispatch.tooltipShow(e);
11267 stacked.dispatch.on('tooltipHide', function(e) {
11268 dispatch.tooltipHide(e);
11271 dispatch.on('tooltipHide', function() {
11272 if (tooltips) nv.tooltip.cleanup();
11275 //============================================================
11276 // Expose Public Variables
11277 //------------------------------------------------------------
11279 // expose chart's sub-components
11280 chart.dispatch = dispatch;
11281 chart.stacked = stacked;
11282 chart.legend = legend;
11283 chart.controls = controls;
11284 chart.xAxis = xAxis;
11285 chart.yAxis = yAxis;
11286 chart.interactiveLayer = interactiveLayer;
11288 yAxis.setTickFormat = yAxis.tickFormat;
11290 chart.dispatch = dispatch;
11291 chart.options = nv.utils.optionsFunc.bind(chart);
11293 chart._options = Object.create({}, {
11294 // simple options, just get/set the necessary values
11295 width: {get: function(){return width;}, set: function(_){width=_;}},
11296 height: {get: function(){return height;}, set: function(_){height=_;}},
11297 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
11298 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
11299 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
11300 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
11301 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
11302 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
11303 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
11304 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
11305 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
11306 yAxisTickFormat: {get: function(){return yAxisTickFormat;}, set: function(_){yAxisTickFormat=_;}},
11308 // options that require extra logic in the setter
11309 margin: {get: function(){return margin;}, set: function(_){
11310 margin.top = _.top !== undefined ? _.top : margin.top;
11311 margin.right = _.right !== undefined ? _.right : margin.right;
11312 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11313 margin.left = _.left !== undefined ? _.left : margin.left;
11315 duration: {get: function(){return duration;}, set: function(_){
11317 renderWatch.reset(duration);
11318 stacked.duration(duration);
11319 xAxis.duration(duration);
11320 yAxis.duration(duration);
11322 color: {get: function(){return color;}, set: function(_){
11323 color = nv.utils.getColor(_);
11324 legend.color(color);
11325 stacked.color(color);
11327 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
11328 rightAlignYAxis = _;
11329 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
11331 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
11332 useInteractiveGuideline = !!_;
11334 chart.interactive(false);
11335 chart.useVoronoi(false);
11340 nv.utils.inheritOptions(chart, stacked);
11341 nv.utils.initOptions(chart);
11346 nv.version = "1.7.0";