1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
4 // set up main nv object
7 // the major global objects under the nv namespace
8 nv.dev = false; //set false when in production
9 nv.tooltip = nv.tooltip || {}; // For the tooltip system
10 nv.utils = nv.utils || {}; // Utility subsystem
11 nv.models = nv.models || {}; //stores all the possible models/components
12 nv.charts = {}; //stores all the ready to use charts
13 nv.logs = {}; //stores some statistics and potential error messages
14 nv.dom = {}; //DOM manipulation functions
16 nv.dispatch = d3.dispatch('render_start', 'render_end');
18 // Function bind polyfill
19 // Needed ONLY for phantomJS as it's missing until version 2.0 which is unreleased as of this comment
20 // https://github.com/ariya/phantomjs/issues/10522
21 // http://kangax.github.io/compat-table/es5/#Function.prototype.bind
22 // phantomJS is used for running the test suite
23 if (!Function.prototype.bind) {
24 Function.prototype.bind = function (oThis) {
25 if (typeof this !== "function") {
26 // closest thing possible to the ECMAScript 5 internal IsCallable function
27 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
30 var aArgs = Array.prototype.slice.call(arguments, 1),
32 fNOP = function () {},
33 fBound = function () {
34 return fToBind.apply(this instanceof fNOP && oThis
37 aArgs.concat(Array.prototype.slice.call(arguments)));
40 fNOP.prototype = this.prototype;
41 fBound.prototype = new fNOP();
46 // Development render timers - disabled if dev = false
48 nv.dispatch.on('render_start', function(e) {
49 nv.logs.startTime = +new Date();
52 nv.dispatch.on('render_end', function(e) {
53 nv.logs.endTime = +new Date();
54 nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
55 nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
59 // Logs all arguments, and returns the last so you can test things in place
60 // Note: in IE8 console.log is an object not a function, and if modernizr is used
61 // then calling Function.prototype.bind with with anything other than a function
62 // causes a TypeError to be thrown.
64 if (nv.dev && window.console && console.log && console.log.apply)
65 console.log.apply(console, arguments);
66 else if (nv.dev && window.console && typeof console.log == "function" && Function.prototype.bind) {
67 var log = Function.prototype.bind.call(console.log, console);
68 log.apply(console, arguments);
70 return arguments[arguments.length - 1];
73 // print console warning, should be used by deprecated functions
74 nv.deprecated = function(name, info) {
75 if (console && console.warn) {
76 console.warn('nvd3 warning: `' + name + '` has been deprecated. ', info || '');
80 // The nv.render function is used to queue up chart rendering
81 // in non-blocking async functions.
82 // When all queued charts are done rendering, nv.dispatch.render_end is invoked.
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 var renderLoop = 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);
98 nv.render.queue.splice(0, i);
100 if (nv.render.queue.length) {
101 setTimeout(renderLoop);
104 nv.dispatch.render_end();
105 nv.render.active = false;
109 setTimeout(renderLoop);
112 nv.render.active = false;
113 nv.render.queue = [];
116 Adds a chart to the async rendering queue. This method can take arguments in two forms:
124 nv.addGraph(<generate Function>, <callback Function>)
126 The generate function should contain code that creates the NVD3 model, sets options
127 on it, adds data to an SVG element, and invokes the chart model. The generate function
128 should return the chart model. See examples/lineChart.html for a usage example.
130 The callback function is optional, and it is called when the generate function completes.
132 nv.addGraph = function(obj) {
133 if (typeof arguments[0] === typeof(Function)) {
134 obj = {generate: arguments[0], callback: arguments[1]};
137 nv.render.queue.push(obj);
139 if (!nv.render.active) {
144 // Node/CommonJS exports
145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
149 if (typeof(window) !== 'undefined') {
152 /* Facade for queueing DOM write operations
\r
153 * with Fastdom (https://github.com/wilsonpage/fastdom)
\r
155 * This could easily be extended to support alternate
\r
156 * implementations in the future.
\r
158 nv.dom.write = function(callback) {
\r
159 if (window.fastdom !== undefined) {
\r
160 return fastdom.write(callback);
\r
165 /* Facade for queueing DOM read operations
\r
166 * with Fastdom (https://github.com/wilsonpage/fastdom)
\r
168 * This could easily be extended to support alternate
\r
169 * implementations in the future.
\r
171 nv.dom.read = function(callback) {
\r
172 if (window.fastdom !== undefined) {
\r
173 return fastdom.read(callback);
\r
176 };/* Utility class to handle creation of an interactive layer.
177 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
178 containing the X-coordinate. It can also render a vertical line where the mouse is located.
180 dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
181 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
182 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
184 nv.interactiveGuideline = function() {
187 var tooltip = nv.models.tooltip();
188 tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false);
194 //Please pass in the bounding chart's top and left margins
195 //This is important for calculating the correct mouseX/Y positions.
196 var margin = {left: 0, top: 0}
197 , xScale = d3.scale.linear()
198 , dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 'elementClick', 'elementDblclick')
199 , showGuideLine = true;
200 //Must pass in the bounding chart's <svg> container.
201 //The mousemove event is attached to this container.
202 var svgContainer = null;
204 // check if IE by looking for activeX
205 var isMSIE = "ActiveXObject" in window;
208 function layer(selection) {
209 selection.each(function(data) {
210 var container = d3.select(this);
211 var availableWidth = (width || 960), availableHeight = (height || 400);
212 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
214 var wrapEnter = wrap.enter()
215 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
216 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
222 function mouseHandler() {
223 var d3mouse = d3.mouse(this);
224 var mouseX = d3mouse[0];
225 var mouseY = d3mouse[1];
226 var subtractMargin = true;
227 var mouseOutAnyReason = false;
230 D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
231 d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
232 over a rect in IE 10.
233 However, d3.event.offsetX/Y also returns the mouse coordinates
234 relative to the triggering <rect>. So we use offsetX/Y on IE.
236 mouseX = d3.event.offsetX;
237 mouseY = d3.event.offsetY;
240 On IE, if you attach a mouse event listener to the <svg> container,
241 it will actually trigger it for all the child elements (like <path>, <circle>, etc).
242 When this happens on IE, the offsetX/Y is set to where ever the child element
244 As a result, we do NOT need to subtract margins to figure out the mouse X/Y
245 position under this scenario. Removing the line below *will* cause
246 the interactive layer to not work right on IE.
248 if(d3.event.target.tagName !== "svg") {
249 subtractMargin = false;
252 if (d3.event.target.className.baseVal.match("nv-legend")) {
253 mouseOutAnyReason = true;
259 mouseX -= margin.left;
260 mouseY -= margin.top;
263 /* If mouseX/Y is outside of the chart's bounds,
264 trigger a mouseOut event.
266 if (mouseX < 0 || mouseY < 0
267 || mouseX > availableWidth || mouseY > availableHeight
268 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
273 if (d3.event.relatedTarget
274 && d3.event.relatedTarget.ownerSVGElement === undefined
275 && (d3.event.relatedTarget.className === undefined
276 || d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
281 dispatch.elementMouseout({
285 layer.renderGuideLine(null); //hide the guideline
286 tooltip.hidden(true);
289 tooltip.hidden(false);
292 var pointXValue = xScale.invert(mouseX);
293 dispatch.elementMousemove({
296 pointXValue: pointXValue
299 //If user double clicks the layer, fire a elementDblclick
300 if (d3.event.type === "dblclick") {
301 dispatch.elementDblclick({
304 pointXValue: pointXValue
308 // if user single clicks the layer, fire elementClick
309 if (d3.event.type === 'click') {
310 dispatch.elementClick({
313 pointXValue: pointXValue
319 .on("touchmove",mouseHandler)
320 .on("mousemove",mouseHandler, true)
321 .on("mouseout" ,mouseHandler,true)
322 .on("dblclick" ,mouseHandler)
323 .on("click", mouseHandler)
326 layer.guideLine = null;
327 //Draws a vertical guideline at the given X postion.
328 layer.renderGuideLine = function(x) {
329 if (!showGuideLine) return;
330 if (layer.guideLine && layer.guideLine.attr("x1") === x) return;
331 nv.dom.write(function() {
332 var line = wrap.select(".nv-interactiveGuideLine")
334 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
337 .attr("class", "nv-guideline")
338 .attr("x1", function(d) { return d;})
339 .attr("x2", function(d) { return d;})
340 .attr("y1", availableHeight)
342 line.exit().remove();
348 layer.dispatch = dispatch;
349 layer.tooltip = tooltip;
351 layer.margin = function(_) {
352 if (!arguments.length) return margin;
353 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
354 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
358 layer.width = function(_) {
359 if (!arguments.length) return width;
364 layer.height = function(_) {
365 if (!arguments.length) return height;
370 layer.xScale = function(_) {
371 if (!arguments.length) return xScale;
376 layer.showGuideLine = function(_) {
377 if (!arguments.length) return showGuideLine;
382 layer.svgContainer = function(_) {
383 if (!arguments.length) return svgContainer;
391 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
392 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
394 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
395 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
396 because 28 is closer to 30 than 10.
398 Unit tests can be found in: interactiveBisectTest.html
400 Has the following known issues:
401 * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
402 * Won't work if there are duplicate x coordinate values.
404 nv.interactiveBisect = function (values, searchVal, xAccessor) {
406 if (! (values instanceof Array)) {
410 if (typeof xAccessor !== 'function') {
411 _xAccessor = function(d) {
415 _xAccessor = xAccessor;
417 var _cmp = function(d, v) {
418 // Accessors are no longer passed the index of the element along with
419 // the element itself when invoked by d3.bisector.
421 // Starting at D3 v3.4.4, d3.bisector() started inspecting the
422 // function passed to determine if it should consider it an accessor
423 // or a comparator. This meant that accessors that take two arguments
424 // (expecting an index as the second parameter) are treated as
425 // comparators where the second argument is the search value against
426 // which the first argument is compared.
427 return _xAccessor(d) - v;
430 var bisect = d3.bisector(_cmp).left;
431 var index = d3.max([0, bisect(values,searchVal) - 1]);
432 var currentValue = _xAccessor(values[index]);
434 if (typeof currentValue === 'undefined') {
435 currentValue = index;
438 if (currentValue === searchVal) {
439 return index; //found exact match
442 var nextIndex = d3.min([index+1, values.length - 1]);
443 var nextValue = _xAccessor(values[nextIndex]);
445 if (typeof nextValue === 'undefined') {
446 nextValue = nextIndex;
449 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
457 Returns the index in the array "values" that is closest to searchVal.
458 Only returns an index if searchVal is within some "threshold".
459 Otherwise, returns null.
461 nv.nearestValueIndex = function (values, searchVal, threshold) {
463 var yDistMax = Infinity, indexToHighlight = null;
464 values.forEach(function(d,i) {
465 var delta = Math.abs(searchVal - d);
466 if ( d != null && delta <= yDistMax && delta < threshold) {
468 indexToHighlight = i;
471 return indexToHighlight;
473 /* Tooltip rendering model for nvd3 charts.
474 window.nv.models.tooltip is the updated,new way to render tooltips.
476 window.nv.tooltip.show is the old tooltip code.
477 window.nv.tooltip.* also has various helper methods.
482 /* Model which can be instantiated to handle tooltip rendering.
484 var tip = nv.models.tooltip().gravity('w').distance(23)
487 tip(); //just invoke the returned function to render tooltip.
489 nv.models.tooltip = function() {
492 Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
493 Example Format of data:
496 value: "August 2009",
498 {key: "Series 1", value: "Value 1", color: "#000"},
499 {key: "Series 2", value: "Value 2", color: "#00f"}
504 var gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
505 , distance = 25 //Distance to offset tooltip from the mouse location.
506 , snapDistance = 0 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
507 , fixedTop = null //If not null, this fixes the top position of the tooltip.
508 , classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
509 , chartContainer = null //Parent dom element of the SVG that holds the chart.
510 , hidden = true // start off hidden, toggle with hide/show functions below
511 , hideDelay = 400 // delay before the tooltip hides after calling hide()
512 , tooltip = null // d3 select of tooltipElem below
513 , tooltipElem = null //actual DOM element representing the tooltip.
514 , position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
515 , offset = {left: 0, top: 0} //Offset of tooltip against the pointer
516 , enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
517 , duration = 100 // duration for tooltip movement
518 , headerEnabled = true
521 // set to true by interactive layer to adjust tooltip positions
522 // eventually we should probably fix interactive layer to get the position better.
523 // for now this is needed if you want to set chartContainer for normal tooltips, else it "fixes" it to broken
524 var isInteractiveLayer = false;
526 //Generates a unique id when you create a new tooltip() object
527 var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
529 //CSS class to specify whether element should not have mouse events.
530 var nvPointerEventsClass = "nv-pointer-events-none";
532 //Format function for the tooltip values column
533 var valueFormatter = function(d,i) {
537 //Format function for the tooltip header value.
538 var headerFormatter = function(d) {
542 var keyFormatter = function(d, i) {
546 //By default, the tooltip model renders a beautiful table inside a DIV.
547 //You can override this function if a custom tooltip is desired.
548 var contentGenerator = function(d) {
553 var table = d3.select(document.createElement("table"));
555 var theadEnter = table.selectAll("thead")
557 .enter().append("thead");
559 theadEnter.append("tr")
563 .classed("x-value", true)
564 .html(headerFormatter(d.value));
567 var tbodyEnter = table.selectAll("tbody")
569 .enter().append("tbody");
571 var trowEnter = tbodyEnter.selectAll("tr")
572 .data(function(p) { return p.series})
575 .classed("highlight", function(p) { return p.highlight});
577 trowEnter.append("td")
578 .classed("legend-color-guide",true)
580 .style("background-color", function(p) { return p.color});
582 trowEnter.append("td")
584 .html(function(p, i) {return keyFormatter(p.key, i)});
586 trowEnter.append("td")
587 .classed("value",true)
588 .html(function(p, i) { return valueFormatter(p.value, i) });
591 trowEnter.selectAll("td").each(function(p) {
593 var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
596 .style("border-bottom-color", opacityScale(opacity))
597 .style("border-top-color", opacityScale(opacity))
602 var html = table.node().outerHTML;
603 if (d.footer !== undefined)
604 html += "<div class='footer'>" + d.footer + "</div>";
609 var dataSeriesExists = function(d) {
611 if (d.series instanceof Array) {
612 return !!d.series.length;
614 // if object, it's okay just convert to array of the object
615 if (d.series instanceof Object) {
616 d.series = [d.series];
623 var calcTooltipPosition = function(pos) {
624 if (!tooltipElem) return;
626 nv.dom.read(function() {
627 var height = parseInt(tooltipElem.offsetHeight, 10),
628 width = parseInt(tooltipElem.offsetWidth, 10),
629 windowWidth = nv.utils.windowSize().width,
630 windowHeight = nv.utils.windowSize().height,
631 scrollTop = window.pageYOffset,
632 scrollLeft = window.pageXOffset,
635 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
636 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
639 //Helper functions to find the total offsets of a given DOM element.
640 //Looks up the entire ancestry of an element, up to the first relatively positioned element.
641 var tooltipTop = function ( Elem ) {
644 if( !isNaN( Elem.offsetTop ) ) {
645 offsetTop += (Elem.offsetTop);
647 Elem = Elem.offsetParent;
651 var tooltipLeft = function ( Elem ) {
652 var offsetLeft = left;
654 if( !isNaN( Elem.offsetLeft ) ) {
655 offsetLeft += (Elem.offsetLeft);
657 Elem = Elem.offsetParent;
662 // calculate position based on gravity
666 left = pos[0] - width - distance;
667 top = pos[1] - (height / 2);
668 tLeft = tooltipLeft(tooltipElem);
669 tTop = tooltipTop(tooltipElem);
670 if (tLeft < scrollLeft) left = pos[0] + distance > scrollLeft ? pos[0] + distance : scrollLeft - tLeft + left;
671 if (tTop < scrollTop) top = scrollTop - tTop + top;
672 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
675 left = pos[0] + distance;
676 top = pos[1] - (height / 2);
677 tLeft = tooltipLeft(tooltipElem);
678 tTop = tooltipTop(tooltipElem);
679 if (tLeft + width > windowWidth) left = pos[0] - width - distance;
680 if (tTop < scrollTop) top = scrollTop + 5;
681 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
684 left = pos[0] - (width / 2) - 5;
685 top = pos[1] + distance;
686 tLeft = tooltipLeft(tooltipElem);
687 tTop = tooltipTop(tooltipElem);
688 if (tLeft < scrollLeft) left = scrollLeft + 5;
689 if (tLeft + width > windowWidth) left = left - width/2 + 5;
690 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
693 left = pos[0] - (width / 2);
694 top = pos[1] - height - distance;
695 tLeft = tooltipLeft(tooltipElem);
696 tTop = tooltipTop(tooltipElem);
697 if (tLeft < scrollLeft) left = scrollLeft + 5;
698 if (tLeft + width > windowWidth) left = left - width/2 + 5;
699 if (scrollTop > tTop) top = scrollTop;
703 top = pos[1] - distance;
704 tLeft = tooltipLeft(tooltipElem);
705 tTop = tooltipTop(tooltipElem);
709 // adjust tooltip offsets
713 // using tooltip.style('transform') returns values un-usable for tween
714 var box = tooltipElem.getBoundingClientRect();
715 var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
716 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
717 var old_translate = 'translate(' + (box.left + scrollLeft) + 'px, ' + (box.top + scrollTop) + 'px)';
718 var new_translate = 'translate(' + left + 'px, ' + top + 'px)';
719 var translateInterpolator = d3.interpolateString(old_translate, new_translate);
721 var is_hidden = tooltip.style('opacity') < 0.1;
723 // delay hiding a bit to avoid flickering
729 .style('opacity', 0);
732 .interrupt() // cancel running transitions
734 .duration(is_hidden ? 0 : duration)
735 // using tween since some versions of d3 can't auto-tween a translate on a div
736 .styleTween('transform', function (d) {
737 return translateInterpolator;
739 // Safari has its own `-webkit-transform` and does not support `transform`
740 // transform tooltip without transition only in Safari
741 .style('-webkit-transform', new_translate)
742 .style('opacity', 1);
750 //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
751 function convertViewBoxRatio() {
752 if (chartContainer) {
753 var svg = d3.select(chartContainer);
754 if (svg.node().tagName !== "svg") {
755 svg = svg.select("svg");
757 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
759 viewBox = viewBox.split(' ');
760 var ratio = parseInt(svg.style('width'), 10) / viewBox[2];
762 position.left = position.left * ratio;
763 position.top = position.top * ratio;
768 //Creates new tooltip container, or uses existing one on DOM.
769 function initTooltip() {
772 if (chartContainer) {
773 body = chartContainer;
775 body = document.body;
777 //Create new tooltip div if it doesn't exist on DOM.
778 tooltip = d3.select(body).append("div")
779 .attr("class", "nvtooltip " + (classes ? classes : "xy-tooltip"))
781 tooltip.style("top", 0).style("left", 0);
782 tooltip.style('opacity', 0);
783 tooltip.selectAll("div, table, td, tr").classed(nvPointerEventsClass, true);
784 tooltip.classed(nvPointerEventsClass, true);
785 tooltipElem = tooltip.node();
789 //Draw the tooltip onto the DOM.
790 function nvtooltip() {
791 if (!enabled) return;
792 if (!dataSeriesExists(data)) return;
794 convertViewBoxRatio();
796 var left = position.left;
797 var top = (fixedTop !== null) ? fixedTop : position.top;
799 nv.dom.write(function () {
801 // generate data and set it into tooltip
802 // Bonus - If you override contentGenerator and return falsey you can use something like
803 // React or Knockout to bind the data for your tooltip
804 var newContent = contentGenerator(data);
806 tooltipElem.innerHTML = newContent;
809 if (chartContainer && isInteractiveLayer) {
810 nv.dom.read(function() {
811 var svgComp = chartContainer.getElementsByTagName("svg")[0];
812 var svgOffset = {left:0,top:0};
814 var svgBound = svgComp.getBoundingClientRect();
815 var chartBound = chartContainer.getBoundingClientRect();
816 var svgBoundTop = svgBound.top;
818 //Defensive code. Sometimes, svgBoundTop can be a really negative
819 // number, like -134254. That's a bug.
820 // If such a number is found, use zero instead. FireFox bug only
821 if (svgBoundTop < 0) {
822 var containerBound = chartContainer.getBoundingClientRect();
823 svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
825 svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
826 svgOffset.left = Math.abs(svgBound.left - chartBound.left);
828 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
829 //You need to also add any offset between the <svg> element and its containing <div>
830 //Finally, add any offset of the containing <div> on the whole page.
831 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
832 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
834 if (snapDistance && snapDistance > 0) {
835 top = Math.floor(top/snapDistance) * snapDistance;
837 calcTooltipPosition([left,top]);
840 calcTooltipPosition([left,top]);
847 nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
848 nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
850 nvtooltip._options = Object.create({}, {
851 // simple read/write options
852 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
853 gravity: {get: function(){return gravity;}, set: function(_){gravity=_;}},
854 distance: {get: function(){return distance;}, set: function(_){distance=_;}},
855 snapDistance: {get: function(){return snapDistance;}, set: function(_){snapDistance=_;}},
856 classes: {get: function(){return classes;}, set: function(_){classes=_;}},
857 chartContainer: {get: function(){return chartContainer;}, set: function(_){chartContainer=_;}},
858 fixedTop: {get: function(){return fixedTop;}, set: function(_){fixedTop=_;}},
859 enabled: {get: function(){return enabled;}, set: function(_){enabled=_;}},
860 hideDelay: {get: function(){return hideDelay;}, set: function(_){hideDelay=_;}},
861 contentGenerator: {get: function(){return contentGenerator;}, set: function(_){contentGenerator=_;}},
862 valueFormatter: {get: function(){return valueFormatter;}, set: function(_){valueFormatter=_;}},
863 headerFormatter: {get: function(){return headerFormatter;}, set: function(_){headerFormatter=_;}},
864 keyFormatter: {get: function(){return keyFormatter;}, set: function(_){keyFormatter=_;}},
865 headerEnabled: {get: function(){return headerEnabled;}, set: function(_){headerEnabled=_;}},
867 // internal use only, set by interactive layer to adjust position.
868 _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}},
870 // options with extra logic
871 position: {get: function(){return position;}, set: function(_){
872 position.left = _.left !== undefined ? _.left : position.left;
873 position.top = _.top !== undefined ? _.top : position.top;
875 offset: {get: function(){return offset;}, set: function(_){
876 offset.left = _.left !== undefined ? _.left : offset.left;
877 offset.top = _.top !== undefined ? _.top : offset.top;
879 hidden: {get: function(){return hidden;}, set: function(_){
885 data: {get: function(){return data;}, set: function(_){
886 // if showing a single data point, adjust data format with that
889 _.series = _.series || {};
890 _.series.value = _.point.y;
891 _.series.color = _.point.color || _.series.color;
896 // read only properties
897 tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}},
898 id: {get: function(){return id;}, set: function(_){}}
901 nv.utils.initOptions(nvtooltip);
909 Gets the browser window size
911 Returns object with height and width properties
913 nv.utils.windowSize = function() {
915 var size = {width: 640, height: 480};
917 // Most recent browsers use
918 if (window.innerWidth && window.innerHeight) {
919 size.width = window.innerWidth;
920 size.height = window.innerHeight;
924 // IE can use depending on mode it is in
925 if (document.compatMode=='CSS1Compat' &&
926 document.documentElement &&
927 document.documentElement.offsetWidth ) {
929 size.width = document.documentElement.offsetWidth;
930 size.height = document.documentElement.offsetHeight;
934 // Earlier IE uses Doc.body
935 if (document.body && document.body.offsetWidth) {
936 size.width = document.body.offsetWidth;
937 size.height = document.body.offsetHeight;
945 Binds callback function to run when window is resized
947 nv.utils.windowResize = function(handler) {
948 if (window.addEventListener) {
949 window.addEventListener('resize', handler);
951 nv.log("ERROR: Failed to bind to window.resize with: ", handler);
953 // return object with clear function to remove the single added callback.
957 window.removeEventListener('resize', handler);
964 Backwards compatible way to implement more d3-like coloring of graphs.
965 Can take in nothing, an array, or a function/scale
966 To use a normal scale, get the range and pass that because we must be able
967 to take two arguments and use the index to keep backward compatibility
969 nv.utils.getColor = function(color) {
970 //if you pass in nothing, get default colors back
971 if (color === undefined) {
972 return nv.utils.defaultColor();
974 //if passed an array, turn it into a color scale
975 // use isArray, instanceof fails if d3 range is created in an iframe
976 } else if(Array.isArray(color)) {
977 var color_scale = d3.scale.ordinal().range(color);
978 return function(d, i) {
979 var key = i === undefined ? d : i;
980 return d.color || color_scale(key);
983 //if passed a function or scale, return it, or whatever it may be
984 //external libs, such as angularjs-nvd3-directives use this
986 //can't really help it if someone passes rubbish as color
993 Default color chooser uses a color scale of 20 colors from D3
994 https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
996 nv.utils.defaultColor = function() {
997 // get range of the scale so we'll turn it into our own function.
998 return nv.utils.getColor(d3.scale.category20().range());
1003 Returns a color function that takes the result of 'getKey' for each series and
1004 looks for a corresponding color from the dictionary
1006 nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
1007 // use default series.key if getKey is undefined
1008 getKey = getKey || function(series) { return series.key };
1009 defaultColors = defaultColors || d3.scale.category20().range();
1011 // start at end of default color list and walk back to index 0
1012 var defIndex = defaultColors.length;
1014 return function(series, index) {
1015 var key = getKey(series);
1016 if (typeof dictionary[key] === 'function') {
1017 return dictionary[key]();
1018 } else if (dictionary[key] !== undefined) {
1019 return dictionary[key];
1021 // no match in dictionary, use a default color
1023 // used all the default colors, start over
1024 defIndex = defaultColors.length;
1026 defIndex = defIndex - 1;
1027 return defaultColors[defIndex];
1034 From the PJAX example on d3js.org, while this is not really directly needed
1035 it's a very cool method for doing pjax, I may expand upon it a little bit,
1036 open to suggestions on anything that may be useful
1038 nv.utils.pjax = function(links, content) {
1040 var load = function(href) {
1041 d3.html(href, function(fragment) {
1042 var target = d3.select(content).node();
1043 target.parentNode.replaceChild(
1044 d3.select(fragment).select(content).node(),
1046 nv.utils.pjax(links, content);
1050 d3.selectAll(links).on("click", function() {
1051 history.pushState(this.href, this.textContent, this.href);
1053 d3.event.preventDefault();
1056 d3.select(window).on("popstate", function() {
1057 if (d3.event.state) {
1058 load(d3.event.state);
1065 For when we want to approximate the width in pixels for an SVG:text element.
1066 Most common instance is when the element is in a display:none; container.
1067 Forumla is : text.length * font-size * constant_factor
1069 nv.utils.calcApproxTextWidth = function (svgTextElem) {
1070 if (typeof svgTextElem.style === 'function'
1071 && typeof svgTextElem.text === 'function') {
1073 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
1074 var textLength = svgTextElem.text().length;
1075 return textLength * fontSize * 0.5;
1082 Numbers that are undefined, null or NaN, convert them to zeros.
1084 nv.utils.NaNtoZero = function(n) {
1085 if (typeof n !== 'number'
1089 || n === -Infinity) {
1097 Add a way to watch for d3 transition ends to d3
1099 d3.selection.prototype.watchTransition = function(renderWatch){
1100 var args = [this].concat([].slice.call(arguments, 1));
1101 return renderWatch.transition.apply(renderWatch, args);
1106 Helper object to watch when d3 has rendered something
1108 nv.utils.renderWatch = function(dispatch, duration) {
1109 if (!(this instanceof nv.utils.renderWatch)) {
1110 return new nv.utils.renderWatch(dispatch, duration);
1113 var _duration = duration !== undefined ? duration : 250;
1114 var renderStack = [];
1117 this.models = function(models) {
1118 models = [].slice.call(arguments, 0);
1119 models.forEach(function(model){
1120 model.__rendered = false;
1122 m.dispatch.on('renderEnd', function(arg){
1123 m.__rendered = true;
1124 self.renderEnd('model');
1128 if (renderStack.indexOf(model) < 0) {
1129 renderStack.push(model);
1135 this.reset = function(duration) {
1136 if (duration !== undefined) {
1137 _duration = duration;
1142 this.transition = function(selection, args, duration) {
1143 args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1145 if (args.length > 1) {
1146 duration = args.pop();
1148 duration = _duration !== undefined ? _duration : 250;
1150 selection.__rendered = false;
1152 if (renderStack.indexOf(selection) < 0) {
1153 renderStack.push(selection);
1156 if (duration === 0) {
1157 selection.__rendered = true;
1158 selection.delay = function() { return this; };
1159 selection.duration = function() { return this; };
1162 if (selection.length === 0) {
1163 selection.__rendered = true;
1164 } else if (selection.every( function(d){ return !d.length; } )) {
1165 selection.__rendered = true;
1167 selection.__rendered = false;
1174 .each(function(){ ++n; })
1175 .each('end', function(d, i) {
1177 selection.__rendered = true;
1178 self.renderEnd.apply(this, args);
1184 this.renderEnd = function() {
1185 if (renderStack.every( function(d){ return d.__rendered; } )) {
1186 renderStack.forEach( function(d){ d.__rendered = false; });
1187 dispatch.renderEnd.apply(this, arguments);
1195 Takes multiple objects and combines them into the first one (dst)
1196 example: nv.utils.deepExtend({a: 1}, {a: 2, b: 3}, {c: 4});
1197 gives: {a: 2, b: 3, c: 4}
1199 nv.utils.deepExtend = function(dst){
1200 var sources = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1201 sources.forEach(function(source) {
1202 for (var key in source) {
1203 var isArray = dst[key] instanceof Array;
1204 var isObject = typeof dst[key] === 'object';
1205 var srcObj = typeof source[key] === 'object';
1207 if (isObject && !isArray && srcObj) {
1208 nv.utils.deepExtend(dst[key], source[key]);
1210 dst[key] = source[key];
1218 state utility object, used to track d3 states in the models
1220 nv.utils.state = function(){
1221 if (!(this instanceof nv.utils.state)) {
1222 return new nv.utils.state();
1226 var _setState = function(){};
1227 var _getState = function(){ return {}; };
1231 this.dispatch = d3.dispatch('change', 'set');
1233 this.dispatch.on('set', function(state){
1234 _setState(state, true);
1237 this.getter = function(fn){
1242 this.setter = function(fn, callback) {
1244 callback = function(){};
1246 _setState = function(state, update){
1255 this.init = function(state){
1257 nv.utils.deepExtend(init, state);
1260 var _set = function(){
1261 var settings = _getState();
1263 if (JSON.stringify(settings) === JSON.stringify(state)) {
1267 for (var key in settings) {
1268 if (state[key] === undefined) {
1271 state[key] = settings[key];
1277 this.update = function(){
1279 _setState(init, false);
1282 if (_set.call(this)) {
1283 this.dispatch.change(state);
1291 Snippet of code you can insert into each nv.models.* to give you the ability to
1298 To enable in the chart:
1299 chart.options = nv.utils.optionsFunc.bind(chart);
1301 nv.utils.optionsFunc = function(args) {
1303 d3.map(args).forEach((function(key,value) {
1304 if (typeof this[key] === "function") {
1314 numTicks: requested number of ticks
1315 data: the chart data
1317 returns the number of ticks to actually use on X axis, based on chart data
1318 to avoid duplicate ticks with the same value
1320 nv.utils.calcTicksX = function(numTicks, data) {
1321 // find max number of values from all data streams
1324 for (i; i < data.length; i += 1) {
1325 var stream_len = data[i] && data[i].values ? data[i].values.length : 0;
1326 numValues = stream_len > numValues ? stream_len : numValues;
1328 nv.log("Requested number of ticks: ", numTicks);
1329 nv.log("Calculated max values to be: ", numValues);
1330 // make sure we don't have more ticks than values to avoid duplicates
1331 numTicks = numTicks > numValues ? numTicks = numValues - 1 : numTicks;
1332 // make sure we have at least one tick
1333 numTicks = numTicks < 1 ? 1 : numTicks;
1334 // make sure it's an integer
1335 numTicks = Math.floor(numTicks);
1336 nv.log("Calculating tick count as: ", numTicks);
1342 returns number of ticks to actually use on Y axis, based on chart data
1344 nv.utils.calcTicksY = function(numTicks, data) {
1345 // currently uses the same logic but we can adjust here if needed later
1346 return nv.utils.calcTicksX(numTicks, data);
1351 Add a particular option from an options object onto chart
1352 Options exposed on a chart are a getter/setter function that returns chart
1353 on set to mimic typical d3 option chaining, e.g. svg.option1('a').option2('b');
1355 option objects should be generated via Object.create() to provide
1356 the option of manipulating data via get/set functions.
1358 nv.utils.initOption = function(chart, name) {
1359 // if it's a call option, just call it directly, otherwise do get/set
1360 if (chart._calls && chart._calls[name]) {
1361 chart[name] = chart._calls[name];
1363 chart[name] = function (_) {
1364 if (!arguments.length) return chart._options[name];
1365 chart._overrides[name] = true;
1366 chart._options[name] = _;
1369 // calling the option as _option will ignore if set by option already
1370 // so nvd3 can set options internally but the stop if set manually
1371 chart['_' + name] = function(_) {
1372 if (!arguments.length) return chart._options[name];
1373 if (!chart._overrides[name]) {
1374 chart._options[name] = _;
1383 Add all options in an options object to the chart
1385 nv.utils.initOptions = function(chart) {
1386 chart._overrides = chart._overrides || {};
1387 var ops = Object.getOwnPropertyNames(chart._options || {});
1388 var calls = Object.getOwnPropertyNames(chart._calls || {});
1389 ops = ops.concat(calls);
1390 for (var i in ops) {
1391 nv.utils.initOption(chart, ops[i]);
1397 Inherit options from a D3 object
1398 d3.rebind makes calling the function on target actually call it on source
1399 Also use _d3options so we can track what we inherit for documentation and chained inheritance
1401 nv.utils.inheritOptionsD3 = function(target, d3_source, oplist) {
1402 target._d3options = oplist.concat(target._d3options || []);
1403 oplist.unshift(d3_source);
1404 oplist.unshift(target);
1405 d3.rebind.apply(this, oplist);
1410 Remove duplicates from an array
1412 nv.utils.arrayUnique = function(a) {
1413 return a.sort().filter(function(item, pos) {
1414 return !pos || item != a[pos - 1];
1420 Keeps a list of custom symbols to draw from in addition to d3.svg.symbol
1421 Necessary since d3 doesn't let you extend its list -_-
1422 Add new symbols by doing nv.utils.symbols.set('name', function(size){...});
1424 nv.utils.symbolMap = d3.map();
1428 Replaces d3.svg.symbol so that we can look both there and our own map
1430 nv.utils.symbol = function() {
1433 function symbol(d,i) {
1434 var t = type.call(this,d,i);
1435 var s = size.call(this,d,i);
1436 if (d3.svg.symbolTypes.indexOf(t) !== -1) {
1437 return d3.svg.symbol().type(t).size(s)();
1439 return nv.utils.symbolMap.get(t)(s);
1442 symbol.type = function(_) {
1443 if (!arguments.length) return type;
1444 type = d3.functor(_);
1447 symbol.size = function(_) {
1448 if (!arguments.length) return size;
1449 size = d3.functor(_);
1457 Inherit option getter/setter functions from source to target
1458 d3.rebind makes calling the function on target actually call it on source
1459 Also track via _inherited and _d3options so we can track what we inherit
1460 for documentation generation purposes and chained inheritance
1462 nv.utils.inheritOptions = function(target, source) {
1463 // inherit all the things
1464 var ops = Object.getOwnPropertyNames(source._options || {});
1465 var calls = Object.getOwnPropertyNames(source._calls || {});
1466 var inherited = source._inherited || [];
1467 var d3ops = source._d3options || [];
1468 var args = ops.concat(calls).concat(inherited).concat(d3ops);
1469 args.unshift(source);
1470 args.unshift(target);
1471 d3.rebind.apply(this, args);
1472 // pass along the lists to keep track of them, don't allow duplicates
1473 target._inherited = nv.utils.arrayUnique(ops.concat(calls).concat(inherited).concat(ops).concat(target._inherited || []));
1474 target._d3options = nv.utils.arrayUnique(d3ops.concat(target._d3options || []));
1479 Runs common initialize code on the svg before the chart builds
1481 nv.utils.initSVG = function(svg) {
1482 svg.classed({'nvd3-svg':true});
1487 Sanitize and provide default for the container height.
1489 nv.utils.sanitizeHeight = function(height, container) {
1490 return (height || parseInt(container.style('height'), 10) || 400);
1495 Sanitize and provide default for the container width.
1497 nv.utils.sanitizeWidth = function(width, container) {
1498 return (width || parseInt(container.style('width'), 10) || 960);
1503 Calculate the available height for a chart.
1505 nv.utils.availableHeight = function(height, container, margin) {
1506 return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom;
1510 Calculate the available width for a chart.
1512 nv.utils.availableWidth = function(width, container, margin) {
1513 return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right;
1517 Clear any rendered chart components and display a chart's 'noData' message
1519 nv.utils.noData = function(chart, container) {
1520 var opt = chart.options(),
1521 margin = opt.margin(),
1522 noData = opt.noData(),
1523 data = (noData == null) ? ["No Data Available."] : [noData],
1524 height = nv.utils.availableHeight(opt.height(), container, margin),
1525 width = nv.utils.availableWidth(opt.width(), container, margin),
1526 x = margin.left + width/2,
1527 y = margin.top + height/2;
1529 //Remove any previously created chart components
1530 container.selectAll('g').remove();
1532 var noDataText = container.selectAll('.nv-noData').data(data);
1534 noDataText.enter().append('text')
1535 .attr('class', 'nvd3 nv-noData')
1536 .attr('dy', '-.7em')
1537 .style('text-anchor', 'middle');
1542 .text(function(t){ return t; });
1545 nv.models.axis = function() {
1548 //============================================================
1549 // Public Variables with Default Settings
1550 //------------------------------------------------------------
1552 var axis = d3.svg.axis();
1553 var scale = d3.scale.linear();
1555 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1556 , width = 75 //only used for tickLabel currently
1557 , height = 60 //only used for tickLabel currently
1558 , axisLabelText = null
1559 , showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1561 , rotateYLabel = true
1562 , staggerLabels = false
1565 , axisLabelDistance = 0
1567 , dispatch = d3.dispatch('renderEnd')
1572 .tickFormat(function(d) { return d })
1575 //============================================================
1576 // Private Variables
1577 //------------------------------------------------------------
1580 var renderWatch = nv.utils.renderWatch(dispatch, duration);
1582 function chart(selection) {
1583 renderWatch.reset();
1584 selection.each(function(data) {
1585 var container = d3.select(this);
1586 nv.utils.initSVG(container);
1588 // Setup containers and skeleton of chart
1589 var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1590 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1591 var gEnter = wrapEnter.append('g');
1592 var g = wrap.select('g');
1596 else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1597 axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1599 //TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1600 g.watchTransition(renderWatch, 'axis').call(axis);
1602 scale0 = scale0 || axis.scale();
1604 var fmt = axis.tickFormat();
1606 fmt = scale0.tickFormat();
1609 var axisLabel = g.selectAll('text.nv-axislabel')
1610 .data([axisLabelText || null]);
1611 axisLabel.exit().remove();
1616 switch (axis.orient()) {
1618 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1619 if (scale.range().length < 2) {
1621 } else if (scale.range().length === 2) {
1622 w = scale.range()[1];
1624 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1627 .attr('text-anchor', 'middle')
1631 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1632 .data(scale.domain());
1633 axisMaxMin.enter().append('g').attr('class',function(d,i){
1634 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
1636 axisMaxMin.exit().remove();
1638 .attr('transform', function(d,i) {
1639 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
1642 .attr('dy', '-0.5em')
1643 .attr('y', -axis.tickPadding())
1644 .attr('text-anchor', 'middle')
1645 .text(function(d,i) {
1647 return ('' + v).match('NaN') ? '' : v;
1649 axisMaxMin.watchTransition(renderWatch, 'min-max top')
1650 .attr('transform', function(d,i) {
1651 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
1656 xLabelMargin = axisLabelDistance + 36;
1657 var maxTextWidth = 30;
1659 var xTicks = g.selectAll('g').select("text");
1660 var rotateLabelsRule = '';
1661 if (rotateLabels%360) {
1662 //Calculate the longest xTick width
1663 xTicks.each(function(d,i){
1664 var box = this.getBoundingClientRect();
1665 var width = box.width;
1666 textHeight = box.height;
1667 if(width > maxTextWidth) maxTextWidth = width;
1669 rotateLabelsRule = 'rotate(' + rotateLabels + ' 0,' + (textHeight/2 + axis.tickPadding()) + ')';
1670 //Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1671 var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1672 xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1675 .attr('transform', rotateLabelsRule)
1676 .style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1678 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1679 if (scale.range().length < 2) {
1681 } else if (scale.range().length === 2) {
1682 w = scale.range()[1];
1684 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1687 .attr('text-anchor', 'middle')
1688 .attr('y', xLabelMargin)
1691 //if (showMaxMin && !isOrdinal) {
1692 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1693 //.data(scale.domain())
1694 .data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1695 axisMaxMin.enter().append('g').attr('class',function(d,i){
1696 return ['nv-axisMaxMin','nv-axisMaxMin-x',(i == 0 ? 'nv-axisMin-x':'nv-axisMax-x')].join(' ')
1698 axisMaxMin.exit().remove();
1700 .attr('transform', function(d,i) {
1701 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1704 .attr('dy', '.71em')
1705 .attr('y', axis.tickPadding())
1706 .attr('transform', rotateLabelsRule)
1707 .style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1708 .text(function(d,i) {
1710 return ('' + v).match('NaN') ? '' : v;
1712 axisMaxMin.watchTransition(renderWatch, 'min-max bottom')
1713 .attr('transform', function(d,i) {
1714 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1719 .attr('transform', function(d,i) {
1720 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
1725 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1727 .style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1728 .attr('transform', rotateYLabel ? 'rotate(90)' : '')
1729 .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
1730 .attr('x', rotateYLabel ? (d3.max(scale.range()) / 2) : axis.tickPadding());
1732 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1733 .data(scale.domain());
1734 axisMaxMin.enter().append('g').attr('class',function(d,i){
1735 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
1737 .style('opacity', 0);
1738 axisMaxMin.exit().remove();
1740 .attr('transform', function(d,i) {
1741 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
1744 .attr('dy', '.32em')
1746 .attr('x', axis.tickPadding())
1747 .style('text-anchor', 'start')
1748 .text(function(d, i) {
1750 return ('' + v).match('NaN') ? '' : v;
1752 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1753 .attr('transform', function(d,i) {
1754 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1757 .style('opacity', 1);
1762 //For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1763 var yTicks = g.selectAll('g').select("text");
1764 yTicks.each(function(d,i){
1765 var labelPadding = this.getBoundingClientRect().width + axis.tickPadding() + 16;
1766 if(labelPadding > width) width = labelPadding;
1769 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1771 .style('text-anchor', rotateYLabel ? 'middle' : 'end')
1772 .attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1773 .attr('y', rotateYLabel ? (-Math.max(margin.left, width) + 25 - (axisLabelDistance || 0)) : -10)
1774 .attr('x', rotateYLabel ? (-d3.max(scale.range()) / 2) : -axis.tickPadding());
1776 axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1777 .data(scale.domain());
1778 axisMaxMin.enter().append('g').attr('class',function(d,i){
1779 return ['nv-axisMaxMin','nv-axisMaxMin-y',(i == 0 ? 'nv-axisMin-y':'nv-axisMax-y')].join(' ')
1781 .style('opacity', 0);
1782 axisMaxMin.exit().remove();
1784 .attr('transform', function(d,i) {
1785 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
1788 .attr('dy', '.32em')
1790 .attr('x', -axis.tickPadding())
1791 .attr('text-anchor', 'end')
1792 .text(function(d,i) {
1794 return ('' + v).match('NaN') ? '' : v;
1796 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1797 .attr('transform', function(d,i) {
1798 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1801 .style('opacity', 1);
1805 axisLabel.text(function(d) { return d });
1807 if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1808 //check if max and min overlap other values, if so, hide the values that overlap
1809 g.selectAll('g') // the g's wrapping each tick
1810 .each(function(d,i) {
1811 d3.select(this).select('text').attr('opacity', 1);
1812 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!
1813 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1814 d3.select(this).attr('opacity', 0);
1816 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1820 //if Max and Min = 0 only show min, Issue #281
1821 if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0) {
1822 wrap.selectAll('g.nv-axisMaxMin').style('opacity', function (d, i) {
1828 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1829 var maxMinRange = [];
1830 wrap.selectAll('g.nv-axisMaxMin')
1831 .each(function(d,i) {
1833 if (i) // i== 1, max position
1834 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)
1835 else // i==0, min position
1836 maxMinRange.push(scale(d) + this.getBoundingClientRect().width + 4)
1838 if (i) // i== 1, max position
1839 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)
1840 else // i==0, min position
1841 maxMinRange.push(scale(d) + 4);
1844 // the g's wrapping each tick
1845 g.selectAll('g').each(function(d, i) {
1846 if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1847 if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1848 d3.select(this).remove();
1850 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1855 //Highlight zero tick line
1856 g.selectAll('.tick')
1857 .filter(function (d) {
1859 The filter needs to return only ticks at or near zero.
1860 Numbers like 0.00001 need to count as zero as well,
1861 and the arithmetic trick below solves that.
1863 return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
1865 .classed('zero', true);
1867 //store old scales for use in transitions on update
1868 scale0 = scale.copy();
1872 renderWatch.renderEnd('axis immediate');
1876 //============================================================
1877 // Expose Public Variables
1878 //------------------------------------------------------------
1880 // expose chart's sub-components
1882 chart.dispatch = dispatch;
1884 chart.options = nv.utils.optionsFunc.bind(chart);
1885 chart._options = Object.create({}, {
1886 // simple options, just get/set the necessary values
1887 axisLabelDistance: {get: function(){return axisLabelDistance;}, set: function(_){axisLabelDistance=_;}},
1888 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
1889 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
1890 rotateYLabel: {get: function(){return rotateYLabel;}, set: function(_){rotateYLabel=_;}},
1891 showMaxMin: {get: function(){return showMaxMin;}, set: function(_){showMaxMin=_;}},
1892 axisLabel: {get: function(){return axisLabelText;}, set: function(_){axisLabelText=_;}},
1893 height: {get: function(){return height;}, set: function(_){height=_;}},
1894 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
1895 width: {get: function(){return width;}, set: function(_){width=_;}},
1897 // options that require extra logic in the setter
1898 margin: {get: function(){return margin;}, set: function(_){
1899 margin.top = _.top !== undefined ? _.top : margin.top;
1900 margin.right = _.right !== undefined ? _.right : margin.right;
1901 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
1902 margin.left = _.left !== undefined ? _.left : margin.left;
1904 duration: {get: function(){return duration;}, set: function(_){
1906 renderWatch.reset(duration);
1908 scale: {get: function(){return scale;}, set: function(_){
1911 isOrdinal = typeof scale.rangeBands === 'function';
1912 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1916 nv.utils.initOptions(chart);
1917 nv.utils.inheritOptionsD3(chart, axis, ['orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat']);
1918 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1922 nv.models.boxPlot = function() {
1925 //============================================================
1926 // Public Variables with Default Settings
1927 //------------------------------------------------------------
1929 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1932 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
1933 , x = d3.scale.ordinal()
1934 , y = d3.scale.linear()
1935 , getX = function(d) { return d.x }
1936 , getY = function(d) { return d.y }
1937 , color = nv.utils.defaultColor()
1943 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
1945 , maxBoxWidth = null
1948 //============================================================
1949 // Private Variables
1950 //------------------------------------------------------------
1953 var renderWatch = nv.utils.renderWatch(dispatch, duration);
1955 function chart(selection) {
1956 renderWatch.reset();
1957 selection.each(function(data) {
1958 var availableWidth = width - margin.left - margin.right,
1959 availableHeight = height - margin.top - margin.bottom;
1961 container = d3.select(this);
1962 nv.utils.initSVG(container);
1965 x .domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
1966 .rangeBands(xRange || [0, availableWidth], .1);
1968 // if we know yDomain, no need to calculate
1971 // (y-range is based on quartiles, whiskers and outliers)
1974 var yMin = d3.min(data.map(function(d) {
1977 min_arr.push(d.values.Q1);
1978 if (d.values.hasOwnProperty('whisker_low') && d.values.whisker_low !== null) { min_arr.push(d.values.whisker_low); }
1979 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { min_arr = min_arr.concat(d.values.outliers); }
1981 return d3.min(min_arr);
1985 var yMax = d3.max(data.map(function(d) {
1988 max_arr.push(d.values.Q3);
1989 if (d.values.hasOwnProperty('whisker_high') && d.values.whisker_high !== null) { max_arr.push(d.values.whisker_high); }
1990 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { max_arr = max_arr.concat(d.values.outliers); }
1992 return d3.max(max_arr);
1995 yData = [ yMin, yMax ] ;
1998 y.domain(yDomain || yData);
1999 y.range(yRange || [availableHeight, 0]);
2001 //store old scales if they exist
2003 y0 = y0 || y.copy().range([y(0),y(0)]);
2005 // Setup containers and skeleton of chart
2006 var wrap = container.selectAll('g.nv-wrap').data([data]);
2007 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap');
2008 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2010 var boxplots = wrap.selectAll('.nv-boxplot').data(function(d) { return d });
2011 var boxEnter = boxplots.enter().append('g').style('stroke-opacity', 1e-6).style('fill-opacity', 1e-6);
2013 .attr('class', 'nv-boxplot')
2014 .attr('transform', function(d,i,j) { return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)'; })
2015 .classed('hover', function(d) { return d.hover });
2017 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2018 .style('stroke-opacity', 1)
2019 .style('fill-opacity', .75)
2020 .delay(function(d,i) { return i * duration / data.length })
2021 .attr('transform', function(d,i) {
2022 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05) + ', 0)';
2024 boxplots.exit().remove();
2026 // ----- add the SVG elements for each boxPlot -----
2028 // conditionally append whisker lines
2029 boxEnter.each(function(d,i) {
2030 var box = d3.select(this);
2032 ['low', 'high'].forEach(function(key) {
2033 if (d.values.hasOwnProperty('whisker_' + key) && d.values['whisker_' + key] !== null) {
2035 .style('stroke', (d.color) ? d.color : color(d,i))
2036 .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
2039 .style('stroke', (d.color) ? d.color : color(d,i))
2040 .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
2046 // TODO: support custom colors here
2047 var outliers = boxplots.selectAll('.nv-boxplot-outlier').data(function(d) {
2048 if (d.values.hasOwnProperty('outliers') && d.values.outliers !== null) { return d.values.outliers; }
2051 outliers.enter().append('circle')
2052 .style('fill', function(d,i,j) { return color(d,j) }).style('stroke', function(d,i,j) { return color(d,j) })
2053 .on('mouseover', function(d,i,j) {
2054 d3.select(this).classed('hover', true);
2055 dispatch.elementMouseover({
2056 series: { key: d, color: color(d,j) },
2060 .on('mouseout', function(d,i,j) {
2061 d3.select(this).classed('hover', false);
2062 dispatch.elementMouseout({
2063 series: { key: d, color: color(d,j) },
2067 .on('mousemove', function(d,i) {
2068 dispatch.elementMousemove({e: d3.event});
2071 outliers.attr('class', 'nv-boxplot-outlier');
2073 .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
2074 .attr('cx', x.rangeBand() * .45)
2075 .attr('cy', function(d,i,j) { return y(d); })
2077 outliers.exit().remove();
2079 var box_width = function() { return (maxBoxWidth === null ? x.rangeBand() * .9 : Math.min(75, x.rangeBand() * .9)); };
2080 var box_left = function() { return x.rangeBand() * .45 - box_width()/2; };
2081 var box_right = function() { return x.rangeBand() * .45 + box_width()/2; };
2083 // update whisker lines and ticks
2084 ['low', 'high'].forEach(function(key) {
2085 var endpoint = (key === 'low') ? 'Q1' : 'Q3';
2087 boxplots.select('line.nv-boxplot-whisker.nv-boxplot-' + key)
2088 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2089 .attr('x1', x.rangeBand() * .45 )
2090 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
2091 .attr('x2', x.rangeBand() * .45 )
2092 .attr('y2', function(d,i) { return y(d.values[endpoint]); });
2094 boxplots.select('line.nv-boxplot-tick.nv-boxplot-' + key)
2095 .watchTransition(renderWatch, 'nv-boxplot: boxplots')
2096 .attr('x1', box_left )
2097 .attr('y1', function(d,i) { return y(d.values['whisker_' + key]); })
2098 .attr('x2', box_right )
2099 .attr('y2', function(d,i) { return y(d.values['whisker_' + key]); });
2102 ['low', 'high'].forEach(function(key) {
2103 boxEnter.selectAll('.nv-boxplot-' + key)
2104 .on('mouseover', function(d,i,j) {
2105 d3.select(this).classed('hover', true);
2106 dispatch.elementMouseover({
2107 series: { key: d.values['whisker_' + key], color: color(d,j) },
2111 .on('mouseout', function(d,i,j) {
2112 d3.select(this).classed('hover', false);
2113 dispatch.elementMouseout({
2114 series: { key: d.values['whisker_' + key], color: color(d,j) },
2118 .on('mousemove', function(d,i) {
2119 dispatch.elementMousemove({e: d3.event});
2124 boxEnter.append('rect')
2125 .attr('class', 'nv-boxplot-box')
2127 .on('mouseover', function(d,i) {
2128 d3.select(this).classed('hover', true);
2129 dispatch.elementMouseover({
2133 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
2134 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
2135 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
2142 .on('mouseout', function(d,i) {
2143 d3.select(this).classed('hover', false);
2144 dispatch.elementMouseout({
2148 { key: 'Q3', value: d.values.Q3, color: d.color || color(d,i) },
2149 { key: 'Q2', value: d.values.Q2, color: d.color || color(d,i) },
2150 { key: 'Q1', value: d.values.Q1, color: d.color || color(d,i) }
2157 .on('mousemove', function(d,i) {
2158 dispatch.elementMousemove({e: d3.event});
2162 boxplots.select('rect.nv-boxplot-box')
2163 .watchTransition(renderWatch, 'nv-boxplot: boxes')
2164 .attr('y', function(d,i) { return y(d.values.Q3); })
2165 .attr('width', box_width)
2166 .attr('x', box_left )
2168 .attr('height', function(d,i) { return Math.abs(y(d.values.Q3) - y(d.values.Q1)) || 1 })
2169 .style('fill', function(d,i) { return d.color || color(d,i) })
2170 .style('stroke', function(d,i) { return d.color || color(d,i) });
2173 boxEnter.append('line').attr('class', 'nv-boxplot-median');
2175 boxplots.select('line.nv-boxplot-median')
2176 .watchTransition(renderWatch, 'nv-boxplot: boxplots line')
2177 .attr('x1', box_left)
2178 .attr('y1', function(d,i) { return y(d.values.Q2); })
2179 .attr('x2', box_right)
2180 .attr('y2', function(d,i) { return y(d.values.Q2); });
2182 //store old scales for use in transitions on update
2187 renderWatch.renderEnd('nv-boxplot immediate');
2191 //============================================================
2192 // Expose Public Variables
2193 //------------------------------------------------------------
2195 chart.dispatch = dispatch;
2196 chart.options = nv.utils.optionsFunc.bind(chart);
2198 chart._options = Object.create({}, {
2199 // simple options, just get/set the necessary values
2200 width: {get: function(){return width;}, set: function(_){width=_;}},
2201 height: {get: function(){return height;}, set: function(_){height=_;}},
2202 maxBoxWidth: {get: function(){return maxBoxWidth;}, set: function(_){maxBoxWidth=_;}},
2203 x: {get: function(){return getX;}, set: function(_){getX=_;}},
2204 y: {get: function(){return getY;}, set: function(_){getY=_;}},
2205 xScale: {get: function(){return x;}, set: function(_){x=_;}},
2206 yScale: {get: function(){return y;}, set: function(_){y=_;}},
2207 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
2208 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
2209 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
2210 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
2211 id: {get: function(){return id;}, set: function(_){id=_;}},
2212 // rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
2214 // options that require extra logic in the setter
2215 margin: {get: function(){return margin;}, set: function(_){
2216 margin.top = _.top !== undefined ? _.top : margin.top;
2217 margin.right = _.right !== undefined ? _.right : margin.right;
2218 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2219 margin.left = _.left !== undefined ? _.left : margin.left;
2221 color: {get: function(){return color;}, set: function(_){
2222 color = nv.utils.getColor(_);
2224 duration: {get: function(){return duration;}, set: function(_){
2226 renderWatch.reset(duration);
2230 nv.utils.initOptions(chart);
2234 nv.models.boxPlotChart = function() {
2237 //============================================================
2238 // Public Variables with Default Settings
2239 //------------------------------------------------------------
2241 var boxplot = nv.models.boxPlot()
2242 , xAxis = nv.models.axis()
2243 , yAxis = nv.models.axis()
2246 var margin = {top: 15, right: 10, bottom: 50, left: 60}
2249 , color = nv.utils.getColor()
2252 , rightAlignYAxis = false
2253 , staggerLabels = false
2254 , tooltip = nv.models.tooltip()
2257 , noData = "No Data Available."
2258 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd')
2265 .tickFormat(function(d) { return d })
2268 .orient((rightAlignYAxis) ? 'right' : 'left')
2269 .tickFormat(d3.format(',.1f'))
2272 tooltip.duration(0);
2274 //============================================================
2275 // Private Variables
2276 //------------------------------------------------------------
2278 var renderWatch = nv.utils.renderWatch(dispatch, duration);
2280 function chart(selection) {
2281 renderWatch.reset();
2282 renderWatch.models(boxplot);
2283 if (showXAxis) renderWatch.models(xAxis);
2284 if (showYAxis) renderWatch.models(yAxis);
2286 selection.each(function(data) {
2287 var container = d3.select(this),
2289 nv.utils.initSVG(container);
2290 var availableWidth = (width || parseInt(container.style('width')) || 960)
2291 - margin.left - margin.right,
2292 availableHeight = (height || parseInt(container.style('height')) || 400)
2293 - margin.top - margin.bottom;
2295 chart.update = function() {
2296 dispatch.beforeUpdate();
2297 container.transition().duration(duration).call(chart);
2299 chart.container = this;
2301 // Display No Data message if there's nothing to show. (quartiles required at minimum)
2302 if (!data || !data.length ||
2303 !data.filter(function(d) { return d.values.hasOwnProperty("Q1") && d.values.hasOwnProperty("Q2") && d.values.hasOwnProperty("Q3"); }).length) {
2304 var noDataText = container.selectAll('.nv-noData').data([noData]);
2306 noDataText.enter().append('text')
2307 .attr('class', 'nvd3 nv-noData')
2308 .attr('dy', '-.7em')
2309 .style('text-anchor', 'middle');
2312 .attr('x', margin.left + availableWidth / 2)
2313 .attr('y', margin.top + availableHeight / 2)
2314 .text(function(d) { return d });
2318 container.selectAll('.nv-noData').remove();
2322 x = boxplot.xScale();
2323 y = boxplot.yScale().clamp(true);
2325 // Setup containers and skeleton of chart
2326 var wrap = container.selectAll('g.nv-wrap.nv-boxPlotWithAxes').data([data]);
2327 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-boxPlotWithAxes').append('g');
2328 var defsEnter = gEnter.append('defs');
2329 var g = wrap.select('g');
2331 gEnter.append('g').attr('class', 'nv-x nv-axis');
2332 gEnter.append('g').attr('class', 'nv-y nv-axis')
2333 .append('g').attr('class', 'nv-zeroLine')
2336 gEnter.append('g').attr('class', 'nv-barsWrap');
2338 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2340 if (rightAlignYAxis) {
2341 g.select(".nv-y.nv-axis")
2342 .attr("transform", "translate(" + availableWidth + ",0)");
2345 // Main Chart Component(s)
2347 .width(availableWidth)
2348 .height(availableHeight);
2350 var barsWrap = g.select('.nv-barsWrap')
2351 .datum(data.filter(function(d) { return !d.disabled }))
2353 barsWrap.transition().call(boxplot);
2356 defsEnter.append('clipPath')
2357 .attr('id', 'nv-x-label-clip-' + boxplot.id())
2360 g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
2361 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
2363 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
2369 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
2370 .tickSize(-availableHeight, 0);
2372 g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
2373 g.select('.nv-x.nv-axis').call(xAxis);
2375 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
2376 if (staggerLabels) {
2379 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
2386 .ticks( Math.floor(availableHeight/36) ) // can't use nv.utils.calcTicksY with Object data
2387 .tickSize( -availableWidth, 0);
2389 g.select('.nv-y.nv-axis').call(yAxis);
2393 g.select(".nv-zeroLine line")
2395 .attr("x2",availableWidth)
2400 //============================================================
2401 // Event Handling/Dispatching (in chart's scope)
2402 //------------------------------------------------------------
2405 renderWatch.renderEnd('nv-boxplot chart immediate');
2409 //============================================================
2410 // Event Handling/Dispatching (out of chart's scope)
2411 //------------------------------------------------------------
2413 boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
2414 tooltip.data(evt).hidden(false);
2417 boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
2418 tooltip.data(evt).hidden(true);
2421 boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
2422 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2425 //============================================================
2426 // Expose Public Variables
2427 //------------------------------------------------------------
2429 chart.dispatch = dispatch;
2430 chart.boxplot = boxplot;
2431 chart.xAxis = xAxis;
2432 chart.yAxis = yAxis;
2433 chart.tooltip = tooltip;
2435 chart.options = nv.utils.optionsFunc.bind(chart);
2437 chart._options = Object.create({}, {
2438 // simple options, just get/set the necessary values
2439 width: {get: function(){return width;}, set: function(_){width=_;}},
2440 height: {get: function(){return height;}, set: function(_){height=_;}},
2441 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
2442 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
2443 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
2444 tooltips: {get: function(){return tooltips;}, set: function(_){tooltips=_;}},
2445 tooltipContent: {get: function(){return tooltip;}, set: function(_){tooltip=_;}},
2446 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
2448 // options that require extra logic in the setter
2449 margin: {get: function(){return margin;}, set: function(_){
2450 margin.top = _.top !== undefined ? _.top : margin.top;
2451 margin.right = _.right !== undefined ? _.right : margin.right;
2452 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2453 margin.left = _.left !== undefined ? _.left : margin.left;
2455 duration: {get: function(){return duration;}, set: function(_){
2457 renderWatch.reset(duration);
2458 boxplot.duration(duration);
2459 xAxis.duration(duration);
2460 yAxis.duration(duration);
2462 color: {get: function(){return color;}, set: function(_){
2463 color = nv.utils.getColor(_);
2464 boxplot.color(color);
2466 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
2467 rightAlignYAxis = _;
2468 yAxis.orient( (_) ? 'right' : 'left');
2472 nv.utils.inheritOptions(chart, boxplot);
2473 nv.utils.initOptions(chart);
2477 // Chart design based on the recommendations of Stephen Few. Implementation
2478 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2479 // http://projects.instantcognition.com/protovis/bulletchart/
2481 nv.models.bullet = function() {
2484 //============================================================
2485 // Public Variables with Default Settings
2486 //------------------------------------------------------------
2488 var margin = {top: 0, right: 0, bottom: 0, left: 0}
2489 , orient = 'left' // TODO top & bottom
2491 , ranges = function(d) { return d.ranges }
2492 , markers = function(d) { return d.markers ? d.markers : [0] }
2493 , measures = function(d) { return d.measures }
2494 , rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
2495 , markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
2496 , measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
2497 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
2502 , color = nv.utils.getColor(['#1f77b4'])
2503 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove')
2506 function chart(selection) {
2507 selection.each(function(d, i) {
2508 var availableWidth = width - margin.left - margin.right,
2509 availableHeight = height - margin.top - margin.bottom;
2511 container = d3.select(this);
2512 nv.utils.initSVG(container);
2514 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2515 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2516 measurez = measures.call(this, d, i).slice().sort(d3.descending),
2517 rangeLabelz = rangeLabels.call(this, d, i).slice(),
2518 markerLabelz = markerLabels.call(this, d, i).slice(),
2519 measureLabelz = measureLabels.call(this, d, i).slice();
2522 // Compute the new x-scale.
2523 var x1 = d3.scale.linear()
2524 .domain( d3.extent(d3.merge([forceX, rangez])) )
2525 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2527 // Retrieve the old x-scale, if this is an update.
2528 var x0 = this.__chart__ || d3.scale.linear()
2529 .domain([0, Infinity])
2532 // Stash the new scale.
2533 this.__chart__ = x1;
2535 var rangeMin = d3.min(rangez), //rangez[2]
2536 rangeMax = d3.max(rangez), //rangez[0]
2537 rangeAvg = rangez[1];
2539 // Setup containers and skeleton of chart
2540 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
2541 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
2542 var gEnter = wrapEnter.append('g');
2543 var g = wrap.select('g');
2545 gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
2546 gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
2547 gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
2548 gEnter.append('rect').attr('class', 'nv-measure');
2550 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2552 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2553 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2554 var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
2555 xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
2557 g.select('rect.nv-rangeMax')
2558 .attr('height', availableHeight)
2559 .attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
2560 .attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
2561 .datum(rangeMax > 0 ? rangeMax : rangeMin)
2563 g.select('rect.nv-rangeAvg')
2564 .attr('height', availableHeight)
2565 .attr('width', w1(rangeAvg))
2566 .attr('x', xp1(rangeAvg))
2569 g.select('rect.nv-rangeMin')
2570 .attr('height', availableHeight)
2571 .attr('width', w1(rangeMax))
2572 .attr('x', xp1(rangeMax))
2573 .attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
2574 .attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
2575 .datum(rangeMax > 0 ? rangeMin : rangeMax)
2577 g.select('rect.nv-measure')
2578 .style('fill', color)
2579 .attr('height', availableHeight / 3)
2580 .attr('y', availableHeight / 3)
2581 .attr('width', measurez < 0 ?
2582 x1(0) - x1(measurez[0])
2583 : x1(measurez[0]) - x1(0))
2584 .attr('x', xp1(measurez))
2585 .on('mouseover', function() {
2586 dispatch.elementMouseover({
2588 label: measureLabelz[0] || 'Current',
2589 color: d3.select(this).style("fill")
2592 .on('mousemove', function() {
2593 dispatch.elementMousemove({
2595 label: measureLabelz[0] || 'Current',
2596 color: d3.select(this).style("fill")
2599 .on('mouseout', function() {
2600 dispatch.elementMouseout({
2602 label: measureLabelz[0] || 'Current',
2603 color: d3.select(this).style("fill")
2607 var h3 = availableHeight / 6;
2609 var markerData = markerz.map( function(marker, index) {
2610 return {value: marker, label: markerLabelz[index]}
2613 .selectAll("path.nv-markerTriangle")
2617 .attr('class', 'nv-markerTriangle')
2618 .attr('transform', function(d) { return 'translate(' + x1(d.value) + ',' + (availableHeight / 2) + ')' })
2619 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
2620 .on('mouseover', function(d) {
2621 dispatch.elementMouseover({
2623 label: d.label || 'Previous',
2624 color: d3.select(this).style("fill"),
2625 pos: [x1(d.value), availableHeight/2]
2629 .on('mousemove', function(d) {
2630 dispatch.elementMousemove({
2632 label: d.label || 'Previous',
2633 color: d3.select(this).style("fill")
2636 .on('mouseout', function(d, i) {
2637 dispatch.elementMouseout({
2639 label: d.label || 'Previous',
2640 color: d3.select(this).style("fill")
2644 wrap.selectAll('.nv-range')
2645 .on('mouseover', function(d,i) {
2646 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2647 dispatch.elementMouseover({
2650 color: d3.select(this).style("fill")
2653 .on('mousemove', function() {
2654 dispatch.elementMousemove({
2656 label: measureLabelz[0] || 'Previous',
2657 color: d3.select(this).style("fill")
2660 .on('mouseout', function(d,i) {
2661 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2662 dispatch.elementMouseout({
2665 color: d3.select(this).style("fill")
2673 //============================================================
2674 // Expose Public Variables
2675 //------------------------------------------------------------
2677 chart.dispatch = dispatch;
2678 chart.options = nv.utils.optionsFunc.bind(chart);
2680 chart._options = Object.create({}, {
2681 // simple options, just get/set the necessary values
2682 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
2683 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
2684 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
2685 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
2686 width: {get: function(){return width;}, set: function(_){width=_;}},
2687 height: {get: function(){return height;}, set: function(_){height=_;}},
2688 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
2690 // options that require extra logic in the setter
2691 margin: {get: function(){return margin;}, set: function(_){
2692 margin.top = _.top !== undefined ? _.top : margin.top;
2693 margin.right = _.right !== undefined ? _.right : margin.right;
2694 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2695 margin.left = _.left !== undefined ? _.left : margin.left;
2697 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2699 reverse = orient == 'right' || orient == 'bottom';
2701 color: {get: function(){return color;}, set: function(_){
2702 color = nv.utils.getColor(_);
2706 nv.utils.initOptions(chart);
2712 // Chart design based on the recommendations of Stephen Few. Implementation
2713 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2714 // http://projects.instantcognition.com/protovis/bulletchart/
2715 nv.models.bulletChart = function() {
2718 //============================================================
2719 // Public Variables with Default Settings
2720 //------------------------------------------------------------
2722 var bullet = nv.models.bullet();
2723 var tooltip = nv.models.tooltip();
2725 var orient = 'left' // TODO top & bottom
2727 , margin = {top: 5, right: 40, bottom: 20, left: 120}
2728 , ranges = function(d) { return d.ranges }
2729 , markers = function(d) { return d.markers ? d.markers : [0] }
2730 , measures = function(d) { return d.measures }
2736 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2739 tooltip.duration(0).headerEnabled(false);
2741 function chart(selection) {
2742 selection.each(function(d, i) {
2743 var container = d3.select(this);
2744 nv.utils.initSVG(container);
2746 var availableWidth = nv.utils.availableWidth(width, container, margin),
2747 availableHeight = height - margin.top - margin.bottom,
2750 chart.update = function() { chart(selection) };
2751 chart.container = this;
2753 // Display No Data message if there's nothing to show.
2754 if (!d || !ranges.call(this, d, i)) {
2755 nv.utils.noData(chart, container)
2758 container.selectAll('.nv-noData').remove();
2761 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2762 markerz = markers.call(this, d, i).slice().sort(d3.descending),
2763 measurez = measures.call(this, d, i).slice().sort(d3.descending);
2765 // Setup containers and skeleton of chart
2766 var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2767 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2768 var gEnter = wrapEnter.append('g');
2769 var g = wrap.select('g');
2771 gEnter.append('g').attr('class', 'nv-bulletWrap');
2772 gEnter.append('g').attr('class', 'nv-titles');
2774 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2776 // Compute the new x-scale.
2777 var x1 = d3.scale.linear()
2778 .domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
2779 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2781 // Retrieve the old x-scale, if this is an update.
2782 var x0 = this.__chart__ || d3.scale.linear()
2783 .domain([0, Infinity])
2786 // Stash the new scale.
2787 this.__chart__ = x1;
2789 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2790 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2792 var title = gEnter.select('.nv-titles').append('g')
2793 .attr('text-anchor', 'end')
2794 .attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2795 title.append('text')
2796 .attr('class', 'nv-title')
2797 .text(function(d) { return d.title; });
2799 title.append('text')
2800 .attr('class', 'nv-subtitle')
2802 .text(function(d) { return d.subtitle; });
2805 .width(availableWidth)
2806 .height(availableHeight)
2808 var bulletWrap = g.select('.nv-bulletWrap');
2809 d3.transition(bulletWrap).call(bullet);
2811 // Compute the tick format.
2812 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2814 // Update the tick groups.
2815 var tick = g.selectAll('g.nv-tick')
2816 .data(x1.ticks( ticks ? ticks : (availableWidth / 50) ), function(d) {
2817 return this.textContent || format(d);
2820 // Initialize the ticks with the old scale, x0.
2821 var tickEnter = tick.enter().append('g')
2822 .attr('class', 'nv-tick')
2823 .attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2824 .style('opacity', 1e-6);
2826 tickEnter.append('line')
2827 .attr('y1', availableHeight)
2828 .attr('y2', availableHeight * 7 / 6);
2830 tickEnter.append('text')
2831 .attr('text-anchor', 'middle')
2833 .attr('y', availableHeight * 7 / 6)
2836 // Transition the updating ticks to the new scale, x1.
2837 var tickUpdate = d3.transition(tick)
2838 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2839 .style('opacity', 1);
2841 tickUpdate.select('line')
2842 .attr('y1', availableHeight)
2843 .attr('y2', availableHeight * 7 / 6);
2845 tickUpdate.select('text')
2846 .attr('y', availableHeight * 7 / 6);
2848 // Transition the exiting ticks to the new scale, x1.
2849 d3.transition(tick.exit())
2850 .attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2851 .style('opacity', 1e-6)
2859 //============================================================
2860 // Event Handling/Dispatching (out of chart's scope)
2861 //------------------------------------------------------------
2863 bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
2869 tooltip.data(evt).hidden(false);
2872 bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
2873 tooltip.hidden(true);
2876 bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
2877 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2880 //============================================================
2881 // Expose Public Variables
2882 //------------------------------------------------------------
2884 chart.bullet = bullet;
2885 chart.dispatch = dispatch;
2886 chart.tooltip = tooltip;
2888 chart.options = nv.utils.optionsFunc.bind(chart);
2890 chart._options = Object.create({}, {
2891 // simple options, just get/set the necessary values
2892 ranges: {get: function(){return ranges;}, set: function(_){ranges=_;}}, // ranges (bad, satisfactory, good)
2893 markers: {get: function(){return markers;}, set: function(_){markers=_;}}, // markers (previous, goal)
2894 measures: {get: function(){return measures;}, set: function(_){measures=_;}}, // measures (actual, forecast)
2895 width: {get: function(){return width;}, set: function(_){width=_;}},
2896 height: {get: function(){return height;}, set: function(_){height=_;}},
2897 tickFormat: {get: function(){return tickFormat;}, set: function(_){tickFormat=_;}},
2898 ticks: {get: function(){return ticks;}, set: function(_){ticks=_;}},
2899 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
2901 // deprecated options
2902 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
2903 // deprecated after 1.7.1
2904 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
2905 tooltip.enabled(!!_);
2907 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
2908 // deprecated after 1.7.1
2909 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
2910 tooltip.contentGenerator(_);
2913 // options that require extra logic in the setter
2914 margin: {get: function(){return margin;}, set: function(_){
2915 margin.top = _.top !== undefined ? _.top : margin.top;
2916 margin.right = _.right !== undefined ? _.right : margin.right;
2917 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
2918 margin.left = _.left !== undefined ? _.left : margin.left;
2920 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2922 reverse = orient == 'right' || orient == 'bottom';
2926 nv.utils.inheritOptions(chart, bullet);
2927 nv.utils.initOptions(chart);
2934 nv.models.candlestickBar = function() {
2937 //============================================================
2938 // Public Variables with Default Settings
2939 //------------------------------------------------------------
2941 var margin = {top: 0, right: 0, bottom: 0, left: 0}
2944 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2946 , x = d3.scale.linear()
2947 , y = d3.scale.linear()
2948 , getX = function(d) { return d.x }
2949 , getY = function(d) { return d.y }
2950 , getOpen = function(d) { return d.open }
2951 , getClose = function(d) { return d.close }
2952 , getHigh = function(d) { return d.high }
2953 , getLow = function(d) { return d.low }
2956 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
2958 , color = nv.utils.defaultColor()
2959 , interactive = false
2964 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
2967 //============================================================
2968 // Private Variables
2969 //------------------------------------------------------------
2971 function chart(selection) {
2972 selection.each(function(data) {
2973 container = d3.select(this);
2974 var availableWidth = nv.utils.availableWidth(width, container, margin),
2975 availableHeight = nv.utils.availableHeight(height, container, margin);
2977 nv.utils.initSVG(container);
2979 // Width of the candlestick bars.
2980 var barWidth = (availableWidth / data[0].values.length) * .45;
2983 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
2986 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
2988 x.range(xRange || [5 + barWidth / 2, availableWidth - barWidth / 2 - 5]);
2990 y.domain(yDomain || [
2991 d3.min(data[0].values.map(getLow).concat(forceY)),
2992 d3.max(data[0].values.map(getHigh).concat(forceY))
2994 ).range(yRange || [availableHeight, 0]);
2996 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
2997 if (x.domain()[0] === x.domain()[1])
2999 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3002 if (y.domain()[0] === y.domain()[1])
3004 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3007 // Setup containers and skeleton of chart
3008 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-candlestickBar').data([data[0].values]);
3009 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-candlestickBar');
3010 var defsEnter = wrapEnter.append('defs');
3011 var gEnter = wrapEnter.append('g');
3012 var g = wrap.select('g');
3014 gEnter.append('g').attr('class', 'nv-ticks');
3016 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3019 .on('click', function(d,i) {
3020 dispatch.chartClick({
3028 defsEnter.append('clipPath')
3029 .attr('id', 'nv-chart-clip-path-' + id)
3032 wrap.select('#nv-chart-clip-path-' + id + ' rect')
3033 .attr('width', availableWidth)
3034 .attr('height', availableHeight);
3036 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3038 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
3039 .data(function(d) { return d });
3040 ticks.exit().remove();
3042 // The colors are currently controlled by CSS.
3043 var tickGroups = ticks.enter().append('g')
3044 .attr('class', function(d, i, j) { return (getOpen(d, i) > getClose(d, i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i});
3046 var lines = tickGroups.append('line')
3047 .attr('class', 'nv-candlestick-lines')
3048 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
3050 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
3052 .attr('y2', function(d, i) { return y(getLow(d, i)); });
3054 var rects = tickGroups.append('rect')
3055 .attr('class', 'nv-candlestick-rects nv-bars')
3056 .attr('transform', function(d, i) {
3057 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
3058 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
3063 .attr('width', barWidth)
3064 .attr('height', function(d, i) {
3065 var open = getOpen(d, i);
3066 var close = getClose(d, i);
3067 return open > close ? y(close) - y(open) : y(open) - y(close);
3070 container.selectAll('.nv-candlestick-lines').transition()
3071 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
3073 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
3075 .attr('y2', function(d, i) { return y(getLow(d, i)); });
3077 container.selectAll('.nv-candlestick-rects').transition()
3078 .attr('transform', function(d, i) {
3079 return 'translate(' + (x(getX(d, i)) - barWidth/2) + ','
3080 + (y(getY(d, i)) - (getOpen(d, i) > getClose(d, i) ? (y(getClose(d, i)) - y(getOpen(d, i))) : 0))
3085 .attr('width', barWidth)
3086 .attr('height', function(d, i) {
3087 var open = getOpen(d, i);
3088 var close = getClose(d, i);
3089 return open > close ? y(close) - y(open) : y(open) - y(close);
3097 //Create methods to allow outside functions to highlight a specific bar.
3098 chart.highlightPoint = function(pointIndex, isHoverOver) {
3099 chart.clearHighlights();
3100 container.select(".nv-candlestickBar .nv-tick-0-" + pointIndex)
3101 .classed("hover", isHoverOver)
3105 chart.clearHighlights = function() {
3106 container.select(".nv-candlestickBar .nv-tick.hover")
3107 .classed("hover", false)
3111 //============================================================
3112 // Expose Public Variables
3113 //------------------------------------------------------------
3115 chart.dispatch = dispatch;
3116 chart.options = nv.utils.optionsFunc.bind(chart);
3118 chart._options = Object.create({}, {
3119 // simple options, just get/set the necessary values
3120 width: {get: function(){return width;}, set: function(_){width=_;}},
3121 height: {get: function(){return height;}, set: function(_){height=_;}},
3122 xScale: {get: function(){return x;}, set: function(_){x=_;}},
3123 yScale: {get: function(){return y;}, set: function(_){y=_;}},
3124 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
3125 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
3126 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
3127 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
3128 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
3129 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
3130 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
3131 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
3132 id: {get: function(){return id;}, set: function(_){id=_;}},
3133 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
3135 x: {get: function(){return getX;}, set: function(_){getX=_;}},
3136 y: {get: function(){return getY;}, set: function(_){getY=_;}},
3137 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
3138 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
3139 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
3140 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
3142 // options that require extra logic in the setter
3143 margin: {get: function(){return margin;}, set: function(_){
3144 margin.top = _.top != undefined ? _.top : margin.top;
3145 margin.right = _.right != undefined ? _.right : margin.right;
3146 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
3147 margin.left = _.left != undefined ? _.left : margin.left;
3149 color: {get: function(){return color;}, set: function(_){
3150 color = nv.utils.getColor(_);
3154 nv.utils.initOptions(chart);
3158 nv.models.cumulativeLineChart = function() {
3161 //============================================================
3162 // Public Variables with Default Settings
3163 //------------------------------------------------------------
3165 var lines = nv.models.line()
3166 , xAxis = nv.models.axis()
3167 , yAxis = nv.models.axis()
3168 , legend = nv.models.legend()
3169 , controls = nv.models.legend()
3170 , interactiveLayer = nv.interactiveGuideline()
3171 , tooltip = nv.models.tooltip()
3174 var margin = {top: 30, right: 30, bottom: 50, left: 60}
3175 , color = nv.utils.defaultColor()
3181 , rightAlignYAxis = false
3182 , showControls = true
3183 , useInteractiveGuideline = false
3185 , x //can be accessed via chart.xScale()
3186 , y //can be accessed via chart.yScale()
3188 , state = nv.utils.state()
3189 , defaultState = null
3191 , average = function(d) { return d.average }
3192 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
3193 , transitionDuration = 250
3195 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
3199 state.rescaleY = rescaleY;
3201 xAxis.orient('bottom').tickPadding(7);
3202 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
3204 tooltip.valueFormatter(function(d, i) {
3205 return yAxis.tickFormat()(d, i);
3206 }).headerFormatter(function(d, i) {
3207 return xAxis.tickFormat()(d, i);
3210 controls.updateState(false);
3212 //============================================================
3213 // Private Variables
3214 //------------------------------------------------------------
3216 var dx = d3.scale.linear()
3217 , index = {i: 0, x: 0}
3218 , renderWatch = nv.utils.renderWatch(dispatch, duration)
3221 var stateGetter = function(data) {
3224 active: data.map(function(d) { return !d.disabled }),
3231 var stateSetter = function(data) {
3232 return function(state) {
3233 if (state.index !== undefined)
3234 index.i = state.index;
3235 if (state.rescaleY !== undefined)
3236 rescaleY = state.rescaleY;
3237 if (state.active !== undefined)
3238 data.forEach(function(series,i) {
3239 series.disabled = !state.active[i];
3244 function chart(selection) {
3245 renderWatch.reset();
3246 renderWatch.models(lines);
3247 if (showXAxis) renderWatch.models(xAxis);
3248 if (showYAxis) renderWatch.models(yAxis);
3249 selection.each(function(data) {
3250 var container = d3.select(this);
3251 nv.utils.initSVG(container);
3252 container.classed('nv-chart-' + id, true);
3255 var availableWidth = nv.utils.availableWidth(width, container, margin),
3256 availableHeight = nv.utils.availableHeight(height, container, margin);
3258 chart.update = function() {
3260 container.call(chart);
3262 container.transition().duration(duration).call(chart)
3264 chart.container = this;
3267 .setter(stateSetter(data), chart.update)
3268 .getter(stateGetter(data))
3271 // DEPRECATED set state.disableddisabled
3272 state.disabled = data.map(function(d) { return !!d.disabled });
3274 if (!defaultState) {
3277 for (key in state) {
3278 if (state[key] instanceof Array)
3279 defaultState[key] = state[key].slice(0);
3281 defaultState[key] = state[key];
3285 var indexDrag = d3.behavior.drag()
3286 .on('dragstart', dragStart)
3287 .on('drag', dragMove)
3288 .on('dragend', dragEnd);
3291 function dragStart(d,i) {
3292 d3.select(chart.container)
3293 .style('cursor', 'ew-resize');
3296 function dragMove(d,i) {
3297 index.x = d3.event.x;
3298 index.i = Math.round(dx.invert(index.x));
3302 function dragEnd(d,i) {
3303 d3.select(chart.container)
3304 .style('cursor', 'auto');
3306 // update state and send stateChange with new index
3307 state.index = index.i;
3308 dispatch.stateChange(state);
3311 // Display No Data message if there's nothing to show.
3312 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3313 nv.utils.noData(chart, container)
3316 container.selectAll('.nv-noData').remove();
3324 var seriesDomains = data
3325 .filter(function(series) { return !series.disabled })
3326 .map(function(series,i) {
3327 var initialDomain = d3.extent(series.values, lines.y());
3329 //account for series being disabled when losing 95% or more
3330 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
3333 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
3334 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
3338 var completeDomain = [
3339 d3.min(seriesDomains, function(d) { return d[0] }),
3340 d3.max(seriesDomains, function(d) { return d[1] })
3343 lines.yDomain(completeDomain);
3345 lines.yDomain(null);
3348 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
3349 .range([0, availableWidth])
3352 var data = indexify(index.i, data);
3354 // Setup containers and skeleton of chart
3355 var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
3356 var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
3357 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
3358 var g = wrap.select('g');
3360 gEnter.append('g').attr('class', 'nv-interactive');
3361 gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
3362 gEnter.append('g').attr('class', 'nv-y nv-axis');
3363 gEnter.append('g').attr('class', 'nv-background');
3364 gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
3365 gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
3366 gEnter.append('g').attr('class', 'nv-legendWrap');
3367 gEnter.append('g').attr('class', 'nv-controlsWrap');
3371 legend.width(availableWidth);
3373 g.select('.nv-legendWrap')
3377 if ( margin.top != legend.height()) {
3378 margin.top = legend.height();
3379 availableHeight = nv.utils.availableHeight(height, container, margin);
3382 g.select('.nv-legendWrap')
3383 .attr('transform', 'translate(0,' + (-margin.top) +')')
3388 var controlsData = [
3389 { key: 'Re-scale y-axis', disabled: !rescaleY }
3394 .color(['#444', '#444', '#444'])
3396 .margin({top: 5, right: 0, bottom: 5, left: 20})
3399 g.select('.nv-controlsWrap')
3400 .datum(controlsData)
3401 .attr('transform', 'translate(0,' + (-margin.top) +')')
3405 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3407 if (rightAlignYAxis) {
3408 g.select(".nv-y.nv-axis")
3409 .attr("transform", "translate(" + availableWidth + ",0)");
3412 // Show error if series goes below 100%
3413 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
3415 wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
3416 if (tempDisabled.length) {
3417 wrap.append('text').attr('class', 'tempDisabled')
3418 .attr('x', availableWidth / 2)
3419 .attr('y', '-.71em')
3420 .style('text-anchor', 'end')
3421 .text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
3424 //Set up interactive layer
3425 if (useInteractiveGuideline) {
3427 .width(availableWidth)
3428 .height(availableHeight)
3429 .margin({left:margin.left,top:margin.top})
3430 .svgContainer(container)
3432 wrap.select(".nv-interactive").call(interactiveLayer);
3435 gEnter.select('.nv-background')
3438 g.select('.nv-background rect')
3439 .attr('width', availableWidth)
3440 .attr('height', availableHeight);
3443 //.x(function(d) { return d.x })
3444 .y(function(d) { return d.display.y })
3445 .width(availableWidth)
3446 .height(availableHeight)
3447 .color(data.map(function(d,i) {
3448 return d.color || color(d, i);
3449 }).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
3451 var linesWrap = g.select('.nv-linesWrap')
3452 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
3454 linesWrap.call(lines);
3456 //Store a series index number in the data array.
3457 data.forEach(function(d,i) {
3461 var avgLineData = data.filter(function(d) {
3462 return !d.disabled && !!average(d);
3465 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
3466 .data(avgLineData, function(d) { return d.key; });
3468 var getAvgLineY = function(d) {
3469 //If average lines go off the svg element, clamp them to the svg bounds.
3470 var yVal = y(average(d));
3471 if (yVal < 0) return 0;
3472 if (yVal > availableHeight) return availableHeight;
3478 .style('stroke-width',2)
3479 .style('stroke-dasharray','10,10')
3480 .style('stroke',function (d,i) {
3481 return lines.color()(d,d.seriesIndex);
3484 .attr('x2',availableWidth)
3485 .attr('y1', getAvgLineY)
3486 .attr('y2', getAvgLineY);
3489 .style('stroke-opacity',function(d){
3490 //If average lines go offscreen, make them transparent
3491 var yVal = y(average(d));
3492 if (yVal < 0 || yVal > availableHeight) return 0;
3496 .attr('x2',availableWidth)
3497 .attr('y1', getAvgLineY)
3498 .attr('y2', getAvgLineY);
3500 avgLines.exit().remove();
3503 var indexLine = linesWrap.selectAll('.nv-indexLine')
3505 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
3508 .attr('fill', 'red')
3509 .attr('fill-opacity', .5)
3510 .style("pointer-events","all")
3514 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
3515 .attr('height', availableHeight);
3521 ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
3522 .tickSize(-availableHeight, 0);
3524 g.select('.nv-x.nv-axis')
3525 .attr('transform', 'translate(0,' + y.range()[0] + ')');
3526 g.select('.nv-x.nv-axis')
3533 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3534 .tickSize( -availableWidth, 0);
3536 g.select('.nv-y.nv-axis')
3540 //============================================================
3541 // Event Handling/Dispatching (in chart's scope)
3542 //------------------------------------------------------------
3544 function updateZero() {
3548 //When dragging the index line, turn off line transitions.
3549 // Then turn them back on when done dragging.
3550 var oldDuration = chart.duration();
3553 chart.duration(oldDuration);
3556 g.select('.nv-background rect')
3557 .on('click', function() {
3558 index.x = d3.mouse(this)[0];
3559 index.i = Math.round(dx.invert(index.x));
3561 // update state and send stateChange with new index
3562 state.index = index.i;
3563 dispatch.stateChange(state);
3568 lines.dispatch.on('elementClick', function(e) {
3569 index.i = e.pointIndex;
3570 index.x = dx(index.i);
3572 // update state and send stateChange with new index
3573 state.index = index.i;
3574 dispatch.stateChange(state);
3579 controls.dispatch.on('legendClick', function(d,i) {
3580 d.disabled = !d.disabled;
3581 rescaleY = !d.disabled;
3583 state.rescaleY = rescaleY;
3584 dispatch.stateChange(state);
3588 legend.dispatch.on('stateChange', function(newState) {
3589 for (var key in newState)
3590 state[key] = newState[key];
3591 dispatch.stateChange(state);
3595 interactiveLayer.dispatch.on('elementMousemove', function(e) {
3596 lines.clearHighlights();
3597 var singlePoint, pointIndex, pointXLocation, allData = [];
3600 .filter(function(series, i) {
3601 series.seriesIndex = i;
3602 return !series.disabled;
3604 .forEach(function(series,i) {
3605 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
3606 lines.highlightPoint(i, pointIndex, true);
3607 var point = series.values[pointIndex];
3608 if (typeof point === 'undefined') return;
3609 if (typeof singlePoint === 'undefined') singlePoint = point;
3610 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
3613 value: chart.y()(point, pointIndex),
3614 color: color(series,series.seriesIndex)
3618 //Highlight the tooltip entry based on which point the mouse is closest to.
3619 if (allData.length > 2) {
3620 var yValue = chart.yScale().invert(e.mouseY);
3621 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
3622 var threshold = 0.03 * domainExtent;
3623 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
3624 if (indexToHighlight !== null)
3625 allData[indexToHighlight].highlight = true;
3628 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
3629 interactiveLayer.tooltip
3630 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
3631 .chartContainer(that.parentNode)
3632 .valueFormatter(function(d,i) {
3633 return yAxis.tickFormat()(d);
3642 interactiveLayer.renderGuideLine(pointXLocation);
3645 interactiveLayer.dispatch.on("elementMouseout",function(e) {
3646 lines.clearHighlights();
3649 // Update chart from a state object passed to event handler
3650 dispatch.on('changeState', function(e) {
3651 if (typeof e.disabled !== 'undefined') {
3652 data.forEach(function(series,i) {
3653 series.disabled = e.disabled[i];
3656 state.disabled = e.disabled;
3659 if (typeof e.index !== 'undefined') {
3661 index.x = dx(index.i);
3663 state.index = e.index;
3669 if (typeof e.rescaleY !== 'undefined') {
3670 rescaleY = e.rescaleY;
3678 renderWatch.renderEnd('cumulativeLineChart immediate');
3683 //============================================================
3684 // Event Handling/Dispatching (out of chart's scope)
3685 //------------------------------------------------------------
3687 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
3689 x: chart.x()(evt.point),
3690 y: chart.y()(evt.point),
3691 color: evt.point.color
3694 tooltip.data(evt).position(evt.pos).hidden(false);
3697 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
3698 tooltip.hidden(true)
3701 //============================================================
3703 //------------------------------------------------------------
3705 var indexifyYGetter = null;
3706 /* Normalize the data according to an index point. */
3707 function indexify(idx, data) {
3708 if (!indexifyYGetter) indexifyYGetter = lines.y();
3709 return data.map(function(line, i) {
3713 var indexValue = line.values[idx];
3714 if (indexValue == null) {
3717 var v = indexifyYGetter(indexValue, idx);
3719 //TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
3720 if (v < -.95 && !noErrorCheck) {
3721 //if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
3723 line.tempDisabled = true;
3727 line.tempDisabled = false;
3729 line.values = line.values.map(function(point, pointIndex) {
3730 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
3738 //============================================================
3739 // Expose Public Variables
3740 //------------------------------------------------------------
3742 // expose chart's sub-components
3743 chart.dispatch = dispatch;
3744 chart.lines = lines;
3745 chart.legend = legend;
3746 chart.controls = controls;
3747 chart.xAxis = xAxis;
3748 chart.yAxis = yAxis;
3749 chart.interactiveLayer = interactiveLayer;
3750 chart.state = state;
3751 chart.tooltip = tooltip;
3753 chart.options = nv.utils.optionsFunc.bind(chart);
3755 chart._options = Object.create({}, {
3756 // simple options, just get/set the necessary values
3757 width: {get: function(){return width;}, set: function(_){width=_;}},
3758 height: {get: function(){return height;}, set: function(_){height=_;}},
3759 rescaleY: {get: function(){return rescaleY;}, set: function(_){rescaleY=_;}},
3760 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
3761 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
3762 average: {get: function(){return average;}, set: function(_){average=_;}},
3763 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
3764 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
3765 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
3766 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
3767 noErrorCheck: {get: function(){return noErrorCheck;}, set: function(_){noErrorCheck=_;}},
3769 // deprecated options
3770 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
3771 // deprecated after 1.7.1
3772 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
3773 tooltip.enabled(!!_);
3775 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
3776 // deprecated after 1.7.1
3777 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
3778 tooltip.contentGenerator(_);
3781 // options that require extra logic in the setter
3782 margin: {get: function(){return margin;}, set: function(_){
3783 margin.top = _.top !== undefined ? _.top : margin.top;
3784 margin.right = _.right !== undefined ? _.right : margin.right;
3785 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
3786 margin.left = _.left !== undefined ? _.left : margin.left;
3788 color: {get: function(){return color;}, set: function(_){
3789 color = nv.utils.getColor(_);
3790 legend.color(color);
3792 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
3793 useInteractiveGuideline = _;
3795 chart.interactive(false);
3796 chart.useVoronoi(false);
3799 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
3800 rightAlignYAxis = _;
3801 yAxis.orient( (_) ? 'right' : 'left');
3803 duration: {get: function(){return duration;}, set: function(_){
3805 lines.duration(duration);
3806 xAxis.duration(duration);
3807 yAxis.duration(duration);
3808 renderWatch.reset(duration);
3812 nv.utils.inheritOptions(chart, lines);
3813 nv.utils.initOptions(chart);
3817 //TODO: consider deprecating by adding necessary features to multiBar model
3818 nv.models.discreteBar = function() {
3821 //============================================================
3822 // Public Variables with Default Settings
3823 //------------------------------------------------------------
3825 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3828 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3830 , x = d3.scale.ordinal()
3831 , y = d3.scale.linear()
3832 , getX = function(d) { return d.x }
3833 , getY = function(d) { return d.y }
3834 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
3835 , color = nv.utils.defaultColor()
3836 , showValues = false
3837 , valueFormat = d3.format(',.2f')
3842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
3843 , rectClass = 'discreteBar'
3847 //============================================================
3848 // Private Variables
3849 //------------------------------------------------------------
3852 var renderWatch = nv.utils.renderWatch(dispatch, duration);
3854 function chart(selection) {
3855 renderWatch.reset();
3856 selection.each(function(data) {
3857 var availableWidth = width - margin.left - margin.right,
3858 availableHeight = height - margin.top - margin.bottom;
3860 container = d3.select(this);
3861 nv.utils.initSVG(container);
3863 //add series index to each data point for reference
3864 data.forEach(function(series, i) {
3865 series.values.forEach(function(point) {
3871 // remap and flatten the data for use in calculating the scales' domains
3872 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
3873 data.map(function(d) {
3874 return d.values.map(function(d,i) {
3875 return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
3879 x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
3880 .rangeBands(xRange || [0, availableWidth], .1);
3881 y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
3883 // If showValues, pad the Y axis range to account for label height
3884 if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3885 else y.range(yRange || [availableHeight, 0]);
3887 //store old scales if they exist
3889 y0 = y0 || y.copy().range([y(0),y(0)]);
3891 // Setup containers and skeleton of chart
3892 var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3893 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3894 var gEnter = wrapEnter.append('g');
3895 var g = wrap.select('g');
3897 gEnter.append('g').attr('class', 'nv-groups');
3898 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3900 //TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3901 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3902 .data(function(d) { return d }, function(d) { return d.key });
3903 groups.enter().append('g')
3904 .style('stroke-opacity', 1e-6)
3905 .style('fill-opacity', 1e-6);
3907 .watchTransition(renderWatch, 'discreteBar: exit groups')
3908 .style('stroke-opacity', 1e-6)
3909 .style('fill-opacity', 1e-6)
3912 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3913 .classed('hover', function(d) { return d.hover });
3915 .watchTransition(renderWatch, 'discreteBar: groups')
3916 .style('stroke-opacity', 1)
3917 .style('fill-opacity', .75);
3919 var bars = groups.selectAll('g.nv-bar')
3920 .data(function(d) { return d.values });
3921 bars.exit().remove();
3923 var barsEnter = bars.enter().append('g')
3924 .attr('transform', function(d,i,j) {
3925 return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3927 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3928 d3.select(this).classed('hover', true);
3929 dispatch.elementMouseover({
3932 color: d3.select(this).style("fill")
3935 .on('mouseout', function(d,i) {
3936 d3.select(this).classed('hover', false);
3937 dispatch.elementMouseout({
3940 color: d3.select(this).style("fill")
3943 .on('mousemove', function(d,i) {
3944 dispatch.elementMousemove({
3947 color: d3.select(this).style("fill")
3950 .on('click', function(d,i) {
3951 dispatch.elementClick({
3954 color: d3.select(this).style("fill")
3956 d3.event.stopPropagation();
3958 .on('dblclick', function(d,i) {
3959 dispatch.elementDblClick({
3962 color: d3.select(this).style("fill")
3964 d3.event.stopPropagation();
3967 barsEnter.append('rect')
3969 .attr('width', x.rangeBand() * .9 / data.length )
3972 barsEnter.append('text')
3973 .attr('text-anchor', 'middle')
3977 .text(function(d,i) { return valueFormat(getY(d,i)) })
3978 .watchTransition(renderWatch, 'discreteBar: bars text')
3979 .attr('x', x.rangeBand() * .9 / 2)
3980 .attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3984 bars.selectAll('text').remove();
3988 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3989 .style('fill', function(d,i) { return d.color || color(d,i) })
3990 .style('stroke', function(d,i) { return d.color || color(d,i) })
3992 .attr('class', rectClass)
3993 .watchTransition(renderWatch, 'discreteBar: bars rect')
3994 .attr('width', x.rangeBand() * .9 / data.length);
3995 bars.watchTransition(renderWatch, 'discreteBar: bars')
3996 //.delay(function(d,i) { return i * 1200 / data[0].values.length })
3997 .attr('transform', function(d,i) {
3998 var left = x(getX(d,i)) + x.rangeBand() * .05,
3999 top = getY(d,i) < 0 ?
4001 y(0) - y(getY(d,i)) < 1 ?
4002 y(0) - 1 : //make 1 px positive bars show up above y=0
4005 return 'translate(' + left + ', ' + top + ')'
4008 .attr('height', function(d,i) {
4009 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
4013 //store old scales for use in transitions on update
4019 renderWatch.renderEnd('discreteBar immediate');
4023 //============================================================
4024 // Expose Public Variables
4025 //------------------------------------------------------------
4027 chart.dispatch = dispatch;
4028 chart.options = nv.utils.optionsFunc.bind(chart);
4030 chart._options = Object.create({}, {
4031 // simple options, just get/set the necessary values
4032 width: {get: function(){return width;}, set: function(_){width=_;}},
4033 height: {get: function(){return height;}, set: function(_){height=_;}},
4034 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
4035 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
4036 x: {get: function(){return getX;}, set: function(_){getX=_;}},
4037 y: {get: function(){return getY;}, set: function(_){getY=_;}},
4038 xScale: {get: function(){return x;}, set: function(_){x=_;}},
4039 yScale: {get: function(){return y;}, set: function(_){y=_;}},
4040 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
4041 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
4042 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
4043 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
4044 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
4045 id: {get: function(){return id;}, set: function(_){id=_;}},
4046 rectClass: {get: function(){return rectClass;}, set: function(_){rectClass=_;}},
4048 // options that require extra logic in the setter
4049 margin: {get: function(){return margin;}, set: function(_){
4050 margin.top = _.top !== undefined ? _.top : margin.top;
4051 margin.right = _.right !== undefined ? _.right : margin.right;
4052 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4053 margin.left = _.left !== undefined ? _.left : margin.left;
4055 color: {get: function(){return color;}, set: function(_){
4056 color = nv.utils.getColor(_);
4058 duration: {get: function(){return duration;}, set: function(_){
4060 renderWatch.reset(duration);
4064 nv.utils.initOptions(chart);
4069 nv.models.discreteBarChart = function() {
4072 //============================================================
4073 // Public Variables with Default Settings
4074 //------------------------------------------------------------
4076 var discretebar = nv.models.discreteBar()
4077 , xAxis = nv.models.axis()
4078 , yAxis = nv.models.axis()
4079 , tooltip = nv.models.tooltip()
4082 var margin = {top: 15, right: 10, bottom: 50, left: 60}
4085 , color = nv.utils.getColor()
4088 , rightAlignYAxis = false
4089 , staggerLabels = false
4093 , dispatch = d3.dispatch('beforeUpdate','renderEnd')
4100 .tickFormat(function(d) { return d })
4103 .orient((rightAlignYAxis) ? 'right' : 'left')
4104 .tickFormat(d3.format(',.1f'))
4109 .headerEnabled(false)
4110 .valueFormatter(function(d, i) {
4111 return yAxis.tickFormat()(d, i);
4113 .keyFormatter(function(d, i) {
4114 return xAxis.tickFormat()(d, i);
4117 //============================================================
4118 // Private Variables
4119 //------------------------------------------------------------
4121 var renderWatch = nv.utils.renderWatch(dispatch, duration);
4123 function chart(selection) {
4124 renderWatch.reset();
4125 renderWatch.models(discretebar);
4126 if (showXAxis) renderWatch.models(xAxis);
4127 if (showYAxis) renderWatch.models(yAxis);
4129 selection.each(function(data) {
4130 var container = d3.select(this),
4132 nv.utils.initSVG(container);
4133 var availableWidth = nv.utils.availableWidth(width, container, margin),
4134 availableHeight = nv.utils.availableHeight(height, container, margin);
4136 chart.update = function() {
4137 dispatch.beforeUpdate();
4138 container.transition().duration(duration).call(chart);
4140 chart.container = this;
4142 // Display No Data message if there's nothing to show.
4143 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4144 nv.utils.noData(chart, container);
4147 container.selectAll('.nv-noData').remove();
4151 x = discretebar.xScale();
4152 y = discretebar.yScale().clamp(true);
4154 // Setup containers and skeleton of chart
4155 var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
4156 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
4157 var defsEnter = gEnter.append('defs');
4158 var g = wrap.select('g');
4160 gEnter.append('g').attr('class', 'nv-x nv-axis');
4161 gEnter.append('g').attr('class', 'nv-y nv-axis')
4162 .append('g').attr('class', 'nv-zeroLine')
4165 gEnter.append('g').attr('class', 'nv-barsWrap');
4167 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4169 if (rightAlignYAxis) {
4170 g.select(".nv-y.nv-axis")
4171 .attr("transform", "translate(" + availableWidth + ",0)");
4174 // Main Chart Component(s)
4176 .width(availableWidth)
4177 .height(availableHeight);
4179 var barsWrap = g.select('.nv-barsWrap')
4180 .datum(data.filter(function(d) { return !d.disabled }));
4182 barsWrap.transition().call(discretebar);
4185 defsEnter.append('clipPath')
4186 .attr('id', 'nv-x-label-clip-' + discretebar.id())
4189 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
4190 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
4192 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
4198 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
4199 .tickSize(-availableHeight, 0);
4201 g.select('.nv-x.nv-axis')
4202 .attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
4203 g.select('.nv-x.nv-axis').call(xAxis);
4205 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
4206 if (staggerLabels) {
4209 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
4216 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4217 .tickSize( -availableWidth, 0);
4219 g.select('.nv-y.nv-axis').call(yAxis);
4223 g.select(".nv-zeroLine line")
4225 .attr("x2",availableWidth)
4231 renderWatch.renderEnd('discreteBar chart immediate');
4235 //============================================================
4236 // Event Handling/Dispatching (out of chart's scope)
4237 //------------------------------------------------------------
4239 discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
4241 key: chart.x()(evt.data),
4242 value: chart.y()(evt.data),
4245 tooltip.data(evt).hidden(false);
4248 discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
4249 tooltip.hidden(true);
4252 discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
4253 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
4256 //============================================================
4257 // Expose Public Variables
4258 //------------------------------------------------------------
4260 chart.dispatch = dispatch;
4261 chart.discretebar = discretebar;
4262 chart.xAxis = xAxis;
4263 chart.yAxis = yAxis;
4264 chart.tooltip = tooltip;
4266 chart.options = nv.utils.optionsFunc.bind(chart);
4268 chart._options = Object.create({}, {
4269 // simple options, just get/set the necessary values
4270 width: {get: function(){return width;}, set: function(_){width=_;}},
4271 height: {get: function(){return height;}, set: function(_){height=_;}},
4272 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
4273 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
4274 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
4275 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
4277 // deprecated options
4278 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
4279 // deprecated after 1.7.1
4280 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
4281 tooltip.enabled(!!_);
4283 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
4284 // deprecated after 1.7.1
4285 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
4286 tooltip.contentGenerator(_);
4289 // options that require extra logic in the setter
4290 margin: {get: function(){return margin;}, set: function(_){
4291 margin.top = _.top !== undefined ? _.top : margin.top;
4292 margin.right = _.right !== undefined ? _.right : margin.right;
4293 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4294 margin.left = _.left !== undefined ? _.left : margin.left;
4296 duration: {get: function(){return duration;}, set: function(_){
4298 renderWatch.reset(duration);
4299 discretebar.duration(duration);
4300 xAxis.duration(duration);
4301 yAxis.duration(duration);
4303 color: {get: function(){return color;}, set: function(_){
4304 color = nv.utils.getColor(_);
4305 discretebar.color(color);
4307 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
4308 rightAlignYAxis = _;
4309 yAxis.orient( (_) ? 'right' : 'left');
4313 nv.utils.inheritOptions(chart, discretebar);
4314 nv.utils.initOptions(chart);
4319 nv.models.distribution = function() {
4321 //============================================================
4322 // Public Variables with Default Settings
4323 //------------------------------------------------------------
4325 var margin = {top: 0, right: 0, bottom: 0, left: 0}
4326 , width = 400 //technically width or height depending on x or y....
4328 , axis = 'x' // 'x' or 'y'... horizontal or vertical
4329 , getData = function(d) { return d[axis] } // defaults d.x or d.y
4330 , color = nv.utils.defaultColor()
4331 , scale = d3.scale.linear()
4334 , dispatch = d3.dispatch('renderEnd')
4337 //============================================================
4340 //============================================================
4341 // Private Variables
4342 //------------------------------------------------------------
4345 var renderWatch = nv.utils.renderWatch(dispatch, duration);
4347 //============================================================
4350 function chart(selection) {
4351 renderWatch.reset();
4352 selection.each(function(data) {
4353 var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
4354 naxis = axis == 'x' ? 'y' : 'x',
4355 container = d3.select(this);
4356 nv.utils.initSVG(container);
4358 //------------------------------------------------------------
4361 scale0 = scale0 || scale;
4363 //------------------------------------------------------------
4366 //------------------------------------------------------------
4367 // Setup containers and skeleton of chart
4369 var wrap = container.selectAll('g.nv-distribution').data([data]);
4370 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
4371 var gEnter = wrapEnter.append('g');
4372 var g = wrap.select('g');
4374 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
4376 //------------------------------------------------------------
4379 var distWrap = g.selectAll('g.nv-dist')
4380 .data(function(d) { return d }, function(d) { return d.key });
4382 distWrap.enter().append('g');
4384 .attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
4385 .style('stroke', function(d,i) { return color(d, i) });
4387 var dist = distWrap.selectAll('line.nv-dist' + axis)
4388 .data(function(d) { return d.values })
4389 dist.enter().append('line')
4390 .attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
4391 .attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
4392 renderWatch.transition(distWrap.exit().selectAll('line.nv-dist' + axis), 'dist exit')
4394 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4395 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4396 .style('stroke-opacity', 0)
4399 .attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
4400 .attr(naxis + '1', 0)
4401 .attr(naxis + '2', size);
4402 renderWatch.transition(dist, 'dist')
4404 .attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4405 .attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4408 scale0 = scale.copy();
4411 renderWatch.renderEnd('distribution immediate');
4416 //============================================================
4417 // Expose Public Variables
4418 //------------------------------------------------------------
4419 chart.options = nv.utils.optionsFunc.bind(chart);
4420 chart.dispatch = dispatch;
4422 chart.margin = function(_) {
4423 if (!arguments.length) return margin;
4424 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4425 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4426 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4427 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4431 chart.width = function(_) {
4432 if (!arguments.length) return width;
4437 chart.axis = function(_) {
4438 if (!arguments.length) return axis;
4443 chart.size = function(_) {
4444 if (!arguments.length) return size;
4449 chart.getData = function(_) {
4450 if (!arguments.length) return getData;
4451 getData = d3.functor(_);
4455 chart.scale = function(_) {
4456 if (!arguments.length) return scale;
4461 chart.color = function(_) {
4462 if (!arguments.length) return color;
4463 color = nv.utils.getColor(_);
4467 chart.duration = function(_) {
4468 if (!arguments.length) return duration;
4470 renderWatch.reset(duration);
4473 //============================================================
4478 nv.models.furiousLegend = function() {
4481 //============================================================
4482 // Public Variables with Default Settings
4483 //------------------------------------------------------------
4485 var margin = {top: 5, right: 0, bottom: 5, left: 0}
4488 , getKey = function(d) { return d.key }
4489 , color = nv.utils.getColor()
4491 , padding = 28 //define how much space between legend items. - recommend 32 for furious version
4493 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4494 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4496 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4497 , vers = 'classic' //Options are "classic" and "furious"
4500 function chart(selection) {
4501 selection.each(function(data) {
4502 var availableWidth = width - margin.left - margin.right,
4503 container = d3.select(this);
4504 nv.utils.initSVG(container);
4506 // Setup containers and skeleton of chart
4507 var wrap = container.selectAll('g.nv-legend').data([data]);
4508 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4509 var g = wrap.select('g');
4511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4513 var series = g.selectAll('.nv-series')
4515 if(vers != 'furious') return d;
4517 return d.filter(function(n) {
4518 return expanded ? true : !n.disengaged;
4521 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4525 if(vers == 'classic') {
4526 seriesEnter.append('circle')
4527 .style('stroke-width', 2)
4528 .attr('class','nv-legend-symbol')
4531 seriesShape = series.select('circle');
4532 } else if (vers == 'furious') {
4533 seriesEnter.append('rect')
4534 .style('stroke-width', 2)
4535 .attr('class','nv-legend-symbol')
4539 seriesShape = series.select('rect');
4541 seriesEnter.append('g')
4542 .attr('class', 'nv-check-box')
4543 .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
4544 .attr('transform', 'translate(-10,-8)scale(0.5)');
4546 var seriesCheckbox = series.select('.nv-check-box');
4548 seriesCheckbox.each(function(d,i) {
4549 d3.select(this).selectAll('path')
4550 .attr('stroke', setTextColor(d,i));
4554 seriesEnter.append('text')
4555 .attr('text-anchor', 'start')
4556 .attr('class','nv-legend-text')
4557 .attr('dy', '.32em')
4560 var seriesText = series.select('text.nv-legend-text');
4563 .on('mouseover', function(d,i) {
4564 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
4566 .on('mouseout', function(d,i) {
4567 dispatch.legendMouseout(d,i);
4569 .on('click', function(d,i) {
4570 dispatch.legendClick(d,i);
4571 // make sure we re-get data in case it was modified
4572 var data = series.data();
4574 if(vers =='classic') {
4575 if (radioButtonMode) {
4576 //Radio button mode: set every series to disabled,
4577 // and enable the clicked series.
4578 data.forEach(function(series) { series.disabled = true});
4582 d.disabled = !d.disabled;
4583 if (data.every(function(series) { return series.disabled})) {
4584 //the default behavior of NVD3 legends is, if every single series
4585 // is disabled, turn all series' back on.
4586 data.forEach(function(series) { series.disabled = false});
4589 } else if(vers == 'furious') {
4591 d.disengaged = !d.disengaged;
4592 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
4593 d.disabled = d.disengaged || d.userDisabled;
4594 } else if (!expanded) {
4595 d.disabled = !d.disabled;
4596 d.userDisabled = d.disabled;
4597 var engaged = data.filter(function(d) { return !d.disengaged; });
4598 if (engaged.every(function(series) { return series.userDisabled })) {
4599 //the default behavior of NVD3 legends is, if every single series
4600 // is disabled, turn all series' back on.
4601 data.forEach(function(series) {
4602 series.disabled = series.userDisabled = false;
4607 dispatch.stateChange({
4608 disabled: data.map(function(d) { return !!d.disabled }),
4609 disengaged: data.map(function(d) { return !!d.disengaged })
4614 .on('dblclick', function(d,i) {
4615 if(vers == 'furious' && expanded) return;
4616 dispatch.legendDblclick(d,i);
4618 // make sure we re-get data in case it was modified
4619 var data = series.data();
4620 //the default behavior of NVD3 legends, when double clicking one,
4621 // is to set all other series' to false, and make the double clicked series enabled.
4622 data.forEach(function(series) {
4623 series.disabled = true;
4624 if(vers == 'furious') series.userDisabled = series.disabled;
4627 if(vers == 'furious') d.userDisabled = d.disabled;
4628 dispatch.stateChange({
4629 disabled: data.map(function(d) { return !!d.disabled })
4634 series.classed('nv-disabled', function(d) { return d.userDisabled });
4635 series.exit().remove();
4638 .attr('fill', setTextColor)
4641 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4642 // NEW ALIGNING CODE, TODO: clean up
4655 var seriesWidths = [];
4656 series.each(function(d,i) {
4657 var legendText = d3.select(this).select('text');
4660 nodeTextLength = legendText.node().getComputedTextLength();
4661 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
4662 if(nodeTextLength <= 0) throw Error();
4665 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4668 seriesWidths.push(nodeTextLength + padding);
4671 var seriesPerRow = 0;
4672 var legendWidth = 0;
4673 var columnWidths = [];
4675 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4676 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4677 legendWidth += seriesWidths[seriesPerRow++];
4679 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4681 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4685 for (var k = 0; k < seriesWidths.length; k++) {
4686 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4687 columnWidths[k % seriesPerRow] = seriesWidths[k];
4690 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4695 var xPositions = [];
4696 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4697 xPositions[i] = curX;
4698 curX += columnWidths[i];
4702 .attr('transform', function(d, i) {
4703 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
4706 //position legend as far right as possible within the total width
4708 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4711 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4714 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
4723 .attr('transform', function(d, i) {
4724 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
4727 if (width < margin.left + margin.right + xpos + length) {
4729 ypos += versPadding;
4733 if (newxpos > maxwidth) maxwidth = newxpos;
4735 return 'translate(' + xpos + ',' + ypos + ')';
4738 //position legend as far right as possible within the total width
4739 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4741 height = margin.top + margin.bottom + ypos + 15;
4744 if(vers == 'furious') {
4745 // Size rectangles after text is placed
4747 .attr('width', function(d,i) {
4748 return seriesText[0][i].getComputedTextLength() + 27;
4756 .style('fill', setBGColor)
4757 .style('stroke', function(d,i) { return d.color || color(d, i) });
4760 function setTextColor(d,i) {
4761 if(vers != 'furious') return '#000';
4763 return d.disengaged ? color(d,i) : '#fff';
4764 } else if (!expanded) {
4765 return !!d.disabled ? color(d,i) : '#fff';
4769 function setBGColor(d,i) {
4770 if(expanded && vers == 'furious') {
4771 return d.disengaged ? '#fff' : color(d,i);
4773 return !!d.disabled ? '#fff' : color(d,i);
4780 //============================================================
4781 // Expose Public Variables
4782 //------------------------------------------------------------
4784 chart.dispatch = dispatch;
4785 chart.options = nv.utils.optionsFunc.bind(chart);
4787 chart._options = Object.create({}, {
4788 // simple options, just get/set the necessary values
4789 width: {get: function(){return width;}, set: function(_){width=_;}},
4790 height: {get: function(){return height;}, set: function(_){height=_;}},
4791 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
4792 align: {get: function(){return align;}, set: function(_){align=_;}},
4793 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
4794 padding: {get: function(){return padding;}, set: function(_){padding=_;}},
4795 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
4796 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
4797 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
4798 vers: {get: function(){return vers;}, set: function(_){vers=_;}},
4800 // options that require extra logic in the setter
4801 margin: {get: function(){return margin;}, set: function(_){
4802 margin.top = _.top !== undefined ? _.top : margin.top;
4803 margin.right = _.right !== undefined ? _.right : margin.right;
4804 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
4805 margin.left = _.left !== undefined ? _.left : margin.left;
4807 color: {get: function(){return color;}, set: function(_){
4808 color = nv.utils.getColor(_);
4812 nv.utils.initOptions(chart);
4816 //TODO: consider deprecating and using multibar with single series for this
4817 nv.models.historicalBar = function() {
4820 //============================================================
4821 // Public Variables with Default Settings
4822 //------------------------------------------------------------
4824 var margin = {top: 0, right: 0, bottom: 0, left: 0}
4827 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
4829 , x = d3.scale.linear()
4830 , y = d3.scale.linear()
4831 , getX = function(d) { return d.x }
4832 , getY = function(d) { return d.y }
4837 , color = nv.utils.defaultColor()
4842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
4843 , interactive = true
4846 var renderWatch = nv.utils.renderWatch(dispatch, 0);
4848 function chart(selection) {
4849 selection.each(function(data) {
4850 renderWatch.reset();
4852 container = d3.select(this);
4853 var availableWidth = nv.utils.availableWidth(width, container, margin),
4854 availableHeight = nv.utils.availableHeight(height, container, margin);
4856 nv.utils.initSVG(container);
4859 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
4862 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
4864 x.range(xRange || [0, availableWidth]);
4866 y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
4867 .range(yRange || [availableHeight, 0]);
4869 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
4870 if (x.domain()[0] === x.domain()[1])
4872 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
4875 if (y.domain()[0] === y.domain()[1])
4877 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
4880 // Setup containers and skeleton of chart
4881 var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
4882 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
4883 var defsEnter = wrapEnter.append('defs');
4884 var gEnter = wrapEnter.append('g');
4885 var g = wrap.select('g');
4887 gEnter.append('g').attr('class', 'nv-bars');
4888 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4891 .on('click', function(d,i) {
4892 dispatch.chartClick({
4900 defsEnter.append('clipPath')
4901 .attr('id', 'nv-chart-clip-path-' + id)
4904 wrap.select('#nv-chart-clip-path-' + id + ' rect')
4905 .attr('width', availableWidth)
4906 .attr('height', availableHeight);
4908 g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
4910 var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
4911 .data(function(d) { return d }, function(d,i) {return getX(d,i)});
4912 bars.exit().remove();
4914 bars.enter().append('rect')
4916 .attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
4917 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
4918 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4919 .on('mouseover', function(d,i) {
4920 if (!interactive) return;
4921 d3.select(this).classed('hover', true);
4922 dispatch.elementMouseover({
4925 color: d3.select(this).style("fill")
4929 .on('mouseout', function(d,i) {
4930 if (!interactive) return;
4931 d3.select(this).classed('hover', false);
4932 dispatch.elementMouseout({
4935 color: d3.select(this).style("fill")
4938 .on('mousemove', function(d,i) {
4939 if (!interactive) return;
4940 dispatch.elementMousemove({
4943 color: d3.select(this).style("fill")
4946 .on('click', function(d,i) {
4947 if (!interactive) return;
4948 dispatch.elementClick({
4951 color: d3.select(this).style("fill")
4953 d3.event.stopPropagation();
4955 .on('dblclick', function(d,i) {
4956 if (!interactive) return;
4957 dispatch.elementDblClick({
4960 color: d3.select(this).style("fill")
4962 d3.event.stopPropagation();
4966 .attr('fill', function(d,i) { return color(d, i); })
4967 .attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
4968 .watchTransition(renderWatch, 'bars')
4969 .attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
4970 //TODO: better width calculations that don't assume always uniform data spacing;w
4971 .attr('width', (availableWidth / data[0].values.length) * .9 );
4973 bars.watchTransition(renderWatch, 'bars')
4974 .attr('y', function(d,i) {
4975 var rval = getY(d,i) < 0 ?
4977 y(0) - y(getY(d,i)) < 1 ?
4980 return nv.utils.NaNtoZero(rval);
4982 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
4986 renderWatch.renderEnd('historicalBar immediate');
4990 //Create methods to allow outside functions to highlight a specific bar.
4991 chart.highlightPoint = function(pointIndex, isHoverOver) {
4993 .select(".nv-bars .nv-bar-0-" + pointIndex)
4994 .classed("hover", isHoverOver)
4998 chart.clearHighlights = function() {
5000 .select(".nv-bars .nv-bar.hover")
5001 .classed("hover", false)
5005 //============================================================
5006 // Expose Public Variables
5007 //------------------------------------------------------------
5009 chart.dispatch = dispatch;
5010 chart.options = nv.utils.optionsFunc.bind(chart);
5012 chart._options = Object.create({}, {
5013 // simple options, just get/set the necessary values
5014 width: {get: function(){return width;}, set: function(_){width=_;}},
5015 height: {get: function(){return height;}, set: function(_){height=_;}},
5016 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
5017 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
5018 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
5019 x: {get: function(){return getX;}, set: function(_){getX=_;}},
5020 y: {get: function(){return getY;}, set: function(_){getY=_;}},
5021 xScale: {get: function(){return x;}, set: function(_){x=_;}},
5022 yScale: {get: function(){return y;}, set: function(_){y=_;}},
5023 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
5024 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
5025 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
5026 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
5027 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
5028 id: {get: function(){return id;}, set: function(_){id=_;}},
5029 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
5031 // options that require extra logic in the setter
5032 margin: {get: function(){return margin;}, set: function(_){
5033 margin.top = _.top !== undefined ? _.top : margin.top;
5034 margin.right = _.right !== undefined ? _.right : margin.right;
5035 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5036 margin.left = _.left !== undefined ? _.left : margin.left;
5038 color: {get: function(){return color;}, set: function(_){
5039 color = nv.utils.getColor(_);
5043 nv.utils.initOptions(chart);
5048 nv.models.historicalBarChart = function(bar_model) {
5051 //============================================================
5052 // Public Variables with Default Settings
5053 //------------------------------------------------------------
5055 var bars = bar_model || nv.models.historicalBar()
5056 , xAxis = nv.models.axis()
5057 , yAxis = nv.models.axis()
5058 , legend = nv.models.legend()
5059 , interactiveLayer = nv.interactiveGuideline()
5060 , tooltip = nv.models.tooltip()
5064 var margin = {top: 30, right: 90, bottom: 50, left: 90}
5065 , color = nv.utils.defaultColor()
5068 , showLegend = false
5071 , rightAlignYAxis = false
5072 , useInteractiveGuideline = false
5076 , defaultState = null
5078 , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
5079 , transitionDuration = 250
5082 xAxis.orient('bottom').tickPadding(7);
5083 yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
5086 .headerEnabled(false)
5087 .valueFormatter(function(d, i) {
5088 return yAxis.tickFormat()(d, i);
5090 .headerFormatter(function(d, i) {
5091 return xAxis.tickFormat()(d, i);
5095 //============================================================
5096 // Private Variables
5097 //------------------------------------------------------------
5099 var renderWatch = nv.utils.renderWatch(dispatch, 0);
5101 function chart(selection) {
5102 selection.each(function(data) {
5103 renderWatch.reset();
5104 renderWatch.models(bars);
5105 if (showXAxis) renderWatch.models(xAxis);
5106 if (showYAxis) renderWatch.models(yAxis);
5108 var container = d3.select(this),
5110 nv.utils.initSVG(container);
5111 var availableWidth = nv.utils.availableWidth(width, container, margin),
5112 availableHeight = nv.utils.availableHeight(height, container, margin);
5114 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5115 chart.container = this;
5117 //set state.disabled
5118 state.disabled = data.map(function(d) { return !!d.disabled });
5120 if (!defaultState) {
5123 for (key in state) {
5124 if (state[key] instanceof Array)
5125 defaultState[key] = state[key].slice(0);
5127 defaultState[key] = state[key];
5131 // Display noData message if there's nothing to show.
5132 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5133 nv.utils.noData(chart, container)
5136 container.selectAll('.nv-noData').remove();
5143 // Setup containers and skeleton of chart
5144 var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
5145 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
5146 var g = wrap.select('g');
5148 gEnter.append('g').attr('class', 'nv-x nv-axis');
5149 gEnter.append('g').attr('class', 'nv-y nv-axis');
5150 gEnter.append('g').attr('class', 'nv-barsWrap');
5151 gEnter.append('g').attr('class', 'nv-legendWrap');
5152 gEnter.append('g').attr('class', 'nv-interactive');
5156 legend.width(availableWidth);
5158 g.select('.nv-legendWrap')
5162 if ( margin.top != legend.height()) {
5163 margin.top = legend.height();
5164 availableHeight = nv.utils.availableHeight(height, container, margin);
5167 wrap.select('.nv-legendWrap')
5168 .attr('transform', 'translate(0,' + (-margin.top) +')')
5170 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5172 if (rightAlignYAxis) {
5173 g.select(".nv-y.nv-axis")
5174 .attr("transform", "translate(" + availableWidth + ",0)");
5177 //Set up interactive layer
5178 if (useInteractiveGuideline) {
5180 .width(availableWidth)
5181 .height(availableHeight)
5182 .margin({left:margin.left, top:margin.top})
5183 .svgContainer(container)
5185 wrap.select(".nv-interactive").call(interactiveLayer);
5188 .width(availableWidth)
5189 .height(availableHeight)
5190 .color(data.map(function(d,i) {
5191 return d.color || color(d, i);
5192 }).filter(function(d,i) { return !data[i].disabled }));
5194 var barsWrap = g.select('.nv-barsWrap')
5195 .datum(data.filter(function(d) { return !d.disabled }));
5196 barsWrap.transition().call(bars);
5202 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5203 .tickSize(-availableHeight, 0);
5205 g.select('.nv-x.nv-axis')
5206 .attr('transform', 'translate(0,' + y.range()[0] + ')');
5207 g.select('.nv-x.nv-axis')
5215 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
5216 .tickSize( -availableWidth, 0);
5218 g.select('.nv-y.nv-axis')
5223 //============================================================
5224 // Event Handling/Dispatching (in chart's scope)
5225 //------------------------------------------------------------
5227 interactiveLayer.dispatch.on('elementMousemove', function(e) {
5228 bars.clearHighlights();
5230 var singlePoint, pointIndex, pointXLocation, allData = [];
5232 .filter(function(series, i) {
5233 series.seriesIndex = i;
5234 return !series.disabled;
5236 .forEach(function(series,i) {
5237 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5238 bars.highlightPoint(pointIndex,true);
5239 var point = series.values[pointIndex];
5240 if (point === undefined) return;
5241 if (singlePoint === undefined) singlePoint = point;
5242 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5245 value: chart.y()(point, pointIndex),
5246 color: color(series,series.seriesIndex),
5247 data: series.values[pointIndex]
5251 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5252 interactiveLayer.tooltip
5253 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5254 .chartContainer(that.parentNode)
5255 .valueFormatter(function(d,i) {
5256 return yAxis.tickFormat()(d);
5264 interactiveLayer.renderGuideLine(pointXLocation);
5268 interactiveLayer.dispatch.on("elementMouseout",function(e) {
5269 dispatch.tooltipHide();
5270 bars.clearHighlights();
5273 legend.dispatch.on('legendClick', function(d,i) {
5274 d.disabled = !d.disabled;
5276 if (!data.filter(function(d) { return !d.disabled }).length) {
5277 data.map(function(d) {
5279 wrap.selectAll('.nv-series').classed('disabled', false);
5284 state.disabled = data.map(function(d) { return !!d.disabled });
5285 dispatch.stateChange(state);
5287 selection.transition().call(chart);
5290 legend.dispatch.on('legendDblclick', function(d) {
5291 //Double clicking should always enable current series, and disabled all others.
5292 data.forEach(function(d) {
5297 state.disabled = data.map(function(d) { return !!d.disabled });
5298 dispatch.stateChange(state);
5302 dispatch.on('changeState', function(e) {
5303 if (typeof e.disabled !== 'undefined') {
5304 data.forEach(function(series,i) {
5305 series.disabled = e.disabled[i];
5308 state.disabled = e.disabled;
5315 renderWatch.renderEnd('historicalBarChart immediate');
5319 //============================================================
5320 // Event Handling/Dispatching (out of chart's scope)
5321 //------------------------------------------------------------
5323 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
5325 key: chart.x()(evt.data),
5326 value: chart.y()(evt.data),
5329 tooltip.data(evt).hidden(false);
5332 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
5333 tooltip.hidden(true);
5336 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
5337 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
5340 //============================================================
5341 // Expose Public Variables
5342 //------------------------------------------------------------
5344 // expose chart's sub-components
5345 chart.dispatch = dispatch;
5347 chart.legend = legend;
5348 chart.xAxis = xAxis;
5349 chart.yAxis = yAxis;
5350 chart.interactiveLayer = interactiveLayer;
5351 chart.tooltip = tooltip;
5353 chart.options = nv.utils.optionsFunc.bind(chart);
5355 chart._options = Object.create({}, {
5356 // simple options, just get/set the necessary values
5357 width: {get: function(){return width;}, set: function(_){width=_;}},
5358 height: {get: function(){return height;}, set: function(_){height=_;}},
5359 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
5360 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
5361 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
5362 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
5363 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
5365 // deprecated options
5366 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
5367 // deprecated after 1.7.1
5368 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
5369 tooltip.enabled(!!_);
5371 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
5372 // deprecated after 1.7.1
5373 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
5374 tooltip.contentGenerator(_);
5377 // options that require extra logic in the setter
5378 margin: {get: function(){return margin;}, set: function(_){
5379 margin.top = _.top !== undefined ? _.top : margin.top;
5380 margin.right = _.right !== undefined ? _.right : margin.right;
5381 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5382 margin.left = _.left !== undefined ? _.left : margin.left;
5384 color: {get: function(){return color;}, set: function(_){
5385 color = nv.utils.getColor(_);
5386 legend.color(color);
5389 duration: {get: function(){return transitionDuration;}, set: function(_){
5390 transitionDuration=_;
5391 renderWatch.reset(transitionDuration);
5392 yAxis.duration(transitionDuration);
5393 xAxis.duration(transitionDuration);
5395 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
5396 rightAlignYAxis = _;
5397 yAxis.orient( (_) ? 'right' : 'left');
5399 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
5400 useInteractiveGuideline = _;
5402 chart.interactive(false);
5407 nv.utils.inheritOptions(chart, bars);
5408 nv.utils.initOptions(chart);
5414 // ohlcChart is just a historical chart with ohlc bars and some tweaks
5415 nv.models.ohlcBarChart = function() {
5416 var chart = nv.models.historicalBarChart(nv.models.ohlcBar());
5418 // special default tooltip since we show multiple values per x
5419 chart.useInteractiveGuideline(true);
5420 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
5421 // we assume only one series exists for this chart
5422 var d = data.series[0].data;
5423 // match line colors as defined in nv.d3.css
5424 var color = d.open < d.close ? "2ca02c" : "d62728";
5426 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
5428 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
5429 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
5430 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
5431 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
5437 // candlestickChart is just a historical chart with candlestick bars and some tweaks
5438 nv.models.candlestickBarChart = function() {
5439 var chart = nv.models.historicalBarChart(nv.models.candlestickBar());
5441 // special default tooltip since we show multiple values per x
5442 chart.useInteractiveGuideline(true);
5443 chart.interactiveLayer.tooltip.contentGenerator(function(data) {
5444 // we assume only one series exists for this chart
5445 var d = data.series[0].data;
5446 // match line colors as defined in nv.d3.css
5447 var color = d.open < d.close ? "2ca02c" : "d62728";
5449 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
5451 '<tr><td>open:</td><td>' + chart.yAxis.tickFormat()(d.open) + '</td></tr>' +
5452 '<tr><td>close:</td><td>' + chart.yAxis.tickFormat()(d.close) + '</td></tr>' +
5453 '<tr><td>high</td><td>' + chart.yAxis.tickFormat()(d.high) + '</td></tr>' +
5454 '<tr><td>low:</td><td>' + chart.yAxis.tickFormat()(d.low) + '</td></tr>' +
5459 nv.models.legend = function() {
5462 //============================================================
5463 // Public Variables with Default Settings
5464 //------------------------------------------------------------
5466 var margin = {top: 5, right: 0, bottom: 5, left: 0}
5469 , getKey = function(d) { return d.key }
5470 , color = nv.utils.getColor()
5472 , padding = 32 //define how much space between legend items. - recommend 32 for furious version
5474 , updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
5475 , radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
5477 , dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
5478 , vers = 'classic' //Options are "classic" and "furious"
5481 function chart(selection) {
5482 selection.each(function(data) {
5483 var availableWidth = width - margin.left - margin.right,
5484 container = d3.select(this);
5485 nv.utils.initSVG(container);
5487 // Setup containers and skeleton of chart
5488 var wrap = container.selectAll('g.nv-legend').data([data]);
5489 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
5490 var g = wrap.select('g');
5492 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5494 var series = g.selectAll('.nv-series')
5496 if(vers != 'furious') return d;
5498 return d.filter(function(n) {
5499 return expanded ? true : !n.disengaged;
5503 var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
5515 if(vers == 'classic') {
5516 seriesEnter.append('circle')
5517 .style('stroke-width', 2)
5518 .attr('class','nv-legend-symbol')
5521 seriesShape = series.select('circle');
5522 } else if (vers == 'furious') {
5523 seriesEnter.append('rect')
5524 .style('stroke-width', 2)
5525 .attr('class','nv-legend-symbol')
5529 seriesShape = series.select('.nv-legend-symbol');
5531 seriesEnter.append('g')
5532 .attr('class', 'nv-check-box')
5533 .property('innerHTML','<path d="M0.5,5 L22.5,5 L22.5,26.5 L0.5,26.5 L0.5,5 Z" class="nv-box"></path><path d="M5.5,12.8618467 L11.9185089,19.2803556 L31,0.198864511" class="nv-check"></path>')
5534 .attr('transform', 'translate(-10,-8)scale(0.5)');
5536 var seriesCheckbox = series.select('.nv-check-box');
5538 seriesCheckbox.each(function(d,i) {
5539 d3.select(this).selectAll('path')
5540 .attr('stroke', setTextColor(d,i));
5544 seriesEnter.append('text')
5545 .attr('text-anchor', 'start')
5546 .attr('class','nv-legend-text')
5547 .attr('dy', '.32em')
5550 var seriesText = series.select('text.nv-legend-text');
5553 .on('mouseover', function(d,i) {
5554 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
5556 .on('mouseout', function(d,i) {
5557 dispatch.legendMouseout(d,i);
5559 .on('click', function(d,i) {
5560 dispatch.legendClick(d,i);
5561 // make sure we re-get data in case it was modified
5562 var data = series.data();
5564 if(vers =='classic') {
5565 if (radioButtonMode) {
5566 //Radio button mode: set every series to disabled,
5567 // and enable the clicked series.
5568 data.forEach(function(series) { series.disabled = true});
5572 d.disabled = !d.disabled;
5573 if (data.every(function(series) { return series.disabled})) {
5574 //the default behavior of NVD3 legends is, if every single series
5575 // is disabled, turn all series' back on.
5576 data.forEach(function(series) { series.disabled = false});
5579 } else if(vers == 'furious') {
5581 d.disengaged = !d.disengaged;
5582 d.userDisabled = d.userDisabled == undefined ? !!d.disabled : d.userDisabled;
5583 d.disabled = d.disengaged || d.userDisabled;
5584 } else if (!expanded) {
5585 d.disabled = !d.disabled;
5586 d.userDisabled = d.disabled;
5587 var engaged = data.filter(function(d) { return !d.disengaged; });
5588 if (engaged.every(function(series) { return series.userDisabled })) {
5589 //the default behavior of NVD3 legends is, if every single series
5590 // is disabled, turn all series' back on.
5591 data.forEach(function(series) {
5592 series.disabled = series.userDisabled = false;
5597 dispatch.stateChange({
5598 disabled: data.map(function(d) { return !!d.disabled }),
5599 disengaged: data.map(function(d) { return !!d.disengaged })
5604 .on('dblclick', function(d,i) {
5605 if(vers == 'furious' && expanded) return;
5606 dispatch.legendDblclick(d,i);
5608 // make sure we re-get data in case it was modified
5609 var data = series.data();
5610 //the default behavior of NVD3 legends, when double clicking one,
5611 // is to set all other series' to false, and make the double clicked series enabled.
5612 data.forEach(function(series) {
5613 series.disabled = true;
5614 if(vers == 'furious') series.userDisabled = series.disabled;
5617 if(vers == 'furious') d.userDisabled = d.disabled;
5618 dispatch.stateChange({
5619 disabled: data.map(function(d) { return !!d.disabled })
5624 series.classed('nv-disabled', function(d) { return d.userDisabled });
5625 series.exit().remove();
5628 .attr('fill', setTextColor)
5631 //TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
5632 // NEW ALIGNING CODE, TODO: clean up
5633 var legendWidth = 0;
5636 var seriesWidths = [];
5637 series.each(function(d,i) {
5638 var legendText = d3.select(this).select('text');
5641 nodeTextLength = legendText.node().getComputedTextLength();
5642 // If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
5643 if(nodeTextLength <= 0) throw Error();
5646 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
5649 seriesWidths.push(nodeTextLength + padding);
5652 var seriesPerRow = 0;
5653 var columnWidths = [];
5656 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
5657 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
5658 legendWidth += seriesWidths[seriesPerRow++];
5660 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
5662 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
5666 for (var k = 0; k < seriesWidths.length; k++) {
5667 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
5668 columnWidths[k % seriesPerRow] = seriesWidths[k];
5671 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
5676 var xPositions = [];
5677 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
5678 xPositions[i] = curX;
5679 curX += columnWidths[i];
5683 .attr('transform', function(d, i) {
5684 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
5687 //position legend as far right as possible within the total width
5689 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
5692 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5695 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
5704 .attr('transform', function(d, i) {
5705 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
5708 if (width < margin.left + margin.right + xpos + length) {
5710 ypos += versPadding;
5714 if (newxpos > maxwidth) maxwidth = newxpos;
5716 if(legendWidth < xpos + maxwidth) {
5717 legendWidth = xpos + maxwidth;
5719 return 'translate(' + xpos + ',' + ypos + ')';
5722 //position legend as far right as possible within the total width
5723 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
5725 height = margin.top + margin.bottom + ypos + 15;
5728 if(vers == 'furious') {
5729 // Size rectangles after text is placed
5731 .attr('width', function(d,i) {
5732 return seriesText[0][i].getComputedTextLength() + 27;
5738 // The background for the expanded legend (UI)
5739 gEnter.insert('rect',':first-child')
5740 .attr('class', 'nv-legend-bg')
5741 .attr('fill', '#eee')
5742 // .attr('stroke', '#444')
5745 var seriesBG = g.select('.nv-legend-bg');
5748 .transition().duration(300)
5749 .attr('x', -versPadding )
5750 .attr('width', legendWidth + versPadding - 12)
5751 .attr('height', height + 10)
5752 .attr('y', -margin.top - 10)
5753 .attr('opacity', expanded ? 1 : 0);
5759 .style('fill', setBGColor)
5760 .style('fill-opacity', setBGOpacity)
5761 .style('stroke', setBGColor);
5764 function setTextColor(d,i) {
5765 if(vers != 'furious') return '#000';
5767 return d.disengaged ? '#000' : '#fff';
5768 } else if (!expanded) {
5769 if(!d.color) d.color = color(d,i);
5770 return !!d.disabled ? d.color : '#fff';
5774 function setBGColor(d,i) {
5775 if(expanded && vers == 'furious') {
5776 return d.disengaged ? '#eee' : d.color || color(d,i);
5778 return d.color || color(d,i);
5783 function setBGOpacity(d,i) {
5784 if(expanded && vers == 'furious') {
5787 return !!d.disabled ? 0 : 1;
5794 //============================================================
5795 // Expose Public Variables
5796 //------------------------------------------------------------
5798 chart.dispatch = dispatch;
5799 chart.options = nv.utils.optionsFunc.bind(chart);
5801 chart._options = Object.create({}, {
5802 // simple options, just get/set the necessary values
5803 width: {get: function(){return width;}, set: function(_){width=_;}},
5804 height: {get: function(){return height;}, set: function(_){height=_;}},
5805 key: {get: function(){return getKey;}, set: function(_){getKey=_;}},
5806 align: {get: function(){return align;}, set: function(_){align=_;}},
5807 rightAlign: {get: function(){return rightAlign;}, set: function(_){rightAlign=_;}},
5808 padding: {get: function(){return padding;}, set: function(_){padding=_;}},
5809 updateState: {get: function(){return updateState;}, set: function(_){updateState=_;}},
5810 radioButtonMode: {get: function(){return radioButtonMode;}, set: function(_){radioButtonMode=_;}},
5811 expanded: {get: function(){return expanded;}, set: function(_){expanded=_;}},
5812 vers: {get: function(){return vers;}, set: function(_){vers=_;}},
5814 // options that require extra logic in the setter
5815 margin: {get: function(){return margin;}, set: function(_){
5816 margin.top = _.top !== undefined ? _.top : margin.top;
5817 margin.right = _.right !== undefined ? _.right : margin.right;
5818 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
5819 margin.left = _.left !== undefined ? _.left : margin.left;
5821 color: {get: function(){return color;}, set: function(_){
5822 color = nv.utils.getColor(_);
5826 nv.utils.initOptions(chart);
5831 nv.models.line = function() {
5833 //============================================================
5834 // Public Variables with Default Settings
5835 //------------------------------------------------------------
5837 var scatter = nv.models.scatter()
5840 var margin = {top: 0, right: 0, bottom: 0, left: 0}
5845 , color = nv.utils.defaultColor() // a function that returns a color
5846 , getX = function(d) { return d.x } // accessor to get the x value from a data point
5847 , getY = function(d) { return d.y } // accessor to get the y value from a data point
5848 , 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
5849 , isArea = function(d) { return d.area } // decides if a line is an area or just a line
5850 , clipEdge = false // if true, masks lines within x and y scale
5851 , x //can be accessed via chart.xScale()
5852 , y //can be accessed via chart.yScale()
5853 , interpolate = "linear" // controls the line interpolation
5855 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
5859 .pointSize(16) // default size
5860 .pointDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5863 //============================================================
5866 //============================================================
5867 // Private Variables
5868 //------------------------------------------------------------
5870 var x0, y0 //used to store previous scales
5871 , renderWatch = nv.utils.renderWatch(dispatch, duration)
5874 //============================================================
5877 function chart(selection) {
5878 renderWatch.reset();
5879 renderWatch.models(scatter);
5880 selection.each(function(data) {
5881 container = d3.select(this);
5882 var availableWidth = nv.utils.availableWidth(width, container, margin),
5883 availableHeight = nv.utils.availableHeight(height, container, margin);
5884 nv.utils.initSVG(container);
5887 x = scatter.xScale();
5888 y = scatter.yScale();
5893 // Setup containers and skeleton of chart
5894 var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5895 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5896 var defsEnter = wrapEnter.append('defs');
5897 var gEnter = wrapEnter.append('g');
5898 var g = wrap.select('g');
5900 gEnter.append('g').attr('class', 'nv-groups');
5901 gEnter.append('g').attr('class', 'nv-scatterWrap');
5903 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5906 .width(availableWidth)
5907 .height(availableHeight);
5909 var scatterWrap = wrap.select('.nv-scatterWrap');
5910 scatterWrap.call(scatter);
5912 defsEnter.append('clipPath')
5913 .attr('id', 'nv-edge-clip-' + scatter.id())
5916 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5917 .attr('width', availableWidth)
5918 .attr('height', (availableHeight > 0) ? availableHeight : 0);
5920 g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5922 .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5924 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5925 .data(function(d) { return d }, function(d) { return d.key });
5926 groups.enter().append('g')
5927 .style('stroke-opacity', 1e-6)
5928 .style('stroke-width', function(d) { return d.strokeWidth || strokeWidth })
5929 .style('fill-opacity', 1e-6);
5931 groups.exit().remove();
5934 .attr('class', function(d,i) {
5935 return (d.classed || '') + ' nv-group nv-series-' + i;
5937 .classed('hover', function(d) { return d.hover })
5938 .style('fill', function(d,i){ return color(d, i) })
5939 .style('stroke', function(d,i){ return color(d, i)});
5940 groups.watchTransition(renderWatch, 'line: groups')
5941 .style('stroke-opacity', 1)
5942 .style('fill-opacity', function(d) { return d.fillOpacity || .5});
5944 var areaPaths = groups.selectAll('path.nv-area')
5945 .data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5946 areaPaths.enter().append('path')
5947 .attr('class', 'nv-area')
5948 .attr('d', function(d) {
5949 return d3.svg.area()
5950 .interpolate(interpolate)
5952 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5953 .y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5954 .y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5955 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5956 .apply(this, [d.values])
5958 groups.exit().selectAll('path.nv-area')
5961 areaPaths.watchTransition(renderWatch, 'line: areaPaths')
5962 .attr('d', function(d) {
5963 return d3.svg.area()
5964 .interpolate(interpolate)
5966 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5967 .y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5968 .y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5969 //.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5970 .apply(this, [d.values])
5973 var linePaths = groups.selectAll('path.nv-line')
5974 .data(function(d) { return [d.values] });
5976 linePaths.enter().append('path')
5977 .attr('class', 'nv-line')
5980 .interpolate(interpolate)
5982 .x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5983 .y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5986 linePaths.watchTransition(renderWatch, 'line: linePaths')
5989 .interpolate(interpolate)
5991 .x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5992 .y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5995 //store old scales for use in transitions on update
5999 renderWatch.renderEnd('line immediate');
6004 //============================================================
6005 // Expose Public Variables
6006 //------------------------------------------------------------
6008 chart.dispatch = dispatch;
6009 chart.scatter = scatter;
6010 // Pass through events
6011 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
6012 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
6013 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
6015 chart.options = nv.utils.optionsFunc.bind(chart);
6017 chart._options = Object.create({}, {
6018 // simple options, just get/set the necessary values
6019 width: {get: function(){return width;}, set: function(_){width=_;}},
6020 height: {get: function(){return height;}, set: function(_){height=_;}},
6021 defined: {get: function(){return defined;}, set: function(_){defined=_;}},
6022 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
6023 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
6025 // options that require extra logic in the setter
6026 margin: {get: function(){return margin;}, set: function(_){
6027 margin.top = _.top !== undefined ? _.top : margin.top;
6028 margin.right = _.right !== undefined ? _.right : margin.right;
6029 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6030 margin.left = _.left !== undefined ? _.left : margin.left;
6032 duration: {get: function(){return duration;}, set: function(_){
6034 renderWatch.reset(duration);
6035 scatter.duration(duration);
6037 isArea: {get: function(){return isArea;}, set: function(_){
6038 isArea = d3.functor(_);
6040 x: {get: function(){return getX;}, set: function(_){
6044 y: {get: function(){return getY;}, set: function(_){
6048 color: {get: function(){return color;}, set: function(_){
6049 color = nv.utils.getColor(_);
6050 scatter.color(color);
6054 nv.utils.inheritOptions(chart, scatter);
6055 nv.utils.initOptions(chart);
6059 nv.models.lineChart = function() {
6062 //============================================================
6063 // Public Variables with Default Settings
6064 //------------------------------------------------------------
6066 var lines = nv.models.line()
6067 , xAxis = nv.models.axis()
6068 , yAxis = nv.models.axis()
6069 , legend = nv.models.legend()
6070 , interactiveLayer = nv.interactiveGuideline()
6071 , tooltip = nv.models.tooltip()
6074 var margin = {top: 30, right: 20, bottom: 50, left: 60}
6075 , color = nv.utils.defaultColor()
6081 , rightAlignYAxis = false
6082 , useInteractiveGuideline = false
6085 , state = nv.utils.state()
6086 , defaultState = null
6088 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
6092 // set options on sub-objects for this chart
6093 xAxis.orient('bottom').tickPadding(7);
6094 yAxis.orient(rightAlignYAxis ? 'right' : 'left');
6095 tooltip.valueFormatter(function(d, i) {
6096 return yAxis.tickFormat()(d, i);
6097 }).headerFormatter(function(d, i) {
6098 return xAxis.tickFormat()(d, i);
6102 //============================================================
6103 // Private Variables
6104 //------------------------------------------------------------
6106 var renderWatch = nv.utils.renderWatch(dispatch, duration);
6108 var stateGetter = function(data) {
6111 active: data.map(function(d) { return !d.disabled })
6116 var stateSetter = function(data) {
6117 return function(state) {
6118 if (state.active !== undefined)
6119 data.forEach(function(series,i) {
6120 series.disabled = !state.active[i];
6125 function chart(selection) {
6126 renderWatch.reset();
6127 renderWatch.models(lines);
6128 if (showXAxis) renderWatch.models(xAxis);
6129 if (showYAxis) renderWatch.models(yAxis);
6131 selection.each(function(data) {
6132 var container = d3.select(this),
6134 nv.utils.initSVG(container);
6135 var availableWidth = nv.utils.availableWidth(width, container, margin),
6136 availableHeight = nv.utils.availableHeight(height, container, margin);
6138 chart.update = function() {
6140 container.call(chart);
6142 container.transition().duration(duration).call(chart)
6144 chart.container = this;
6147 .setter(stateSetter(data), chart.update)
6148 .getter(stateGetter(data))
6151 // DEPRECATED set state.disableddisabled
6152 state.disabled = data.map(function(d) { return !!d.disabled });
6154 if (!defaultState) {
6157 for (key in state) {
6158 if (state[key] instanceof Array)
6159 defaultState[key] = state[key].slice(0);
6161 defaultState[key] = state[key];
6165 // Display noData message if there's nothing to show.
6166 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6167 nv.utils.noData(chart, container)
6170 container.selectAll('.nv-noData').remove();
6178 // Setup containers and skeleton of chart
6179 var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
6180 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
6181 var g = wrap.select('g');
6183 gEnter.append("rect").style("opacity",0);
6184 gEnter.append('g').attr('class', 'nv-x nv-axis');
6185 gEnter.append('g').attr('class', 'nv-y nv-axis');
6186 gEnter.append('g').attr('class', 'nv-linesWrap');
6187 gEnter.append('g').attr('class', 'nv-legendWrap');
6188 gEnter.append('g').attr('class', 'nv-interactive');
6191 .attr("width",availableWidth)
6192 .attr("height",(availableHeight > 0) ? availableHeight : 0);
6196 legend.width(availableWidth);
6198 g.select('.nv-legendWrap')
6202 if ( margin.top != legend.height()) {
6203 margin.top = legend.height();
6204 availableHeight = nv.utils.availableHeight(height, container, margin);
6207 wrap.select('.nv-legendWrap')
6208 .attr('transform', 'translate(0,' + (-margin.top) +')')
6211 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6213 if (rightAlignYAxis) {
6214 g.select(".nv-y.nv-axis")
6215 .attr("transform", "translate(" + availableWidth + ",0)");
6218 //Set up interactive layer
6219 if (useInteractiveGuideline) {
6221 .width(availableWidth)
6222 .height(availableHeight)
6223 .margin({left:margin.left, top:margin.top})
6224 .svgContainer(container)
6226 wrap.select(".nv-interactive").call(interactiveLayer);
6230 .width(availableWidth)
6231 .height(availableHeight)
6232 .color(data.map(function(d,i) {
6233 return d.color || color(d, i);
6234 }).filter(function(d,i) { return !data[i].disabled }));
6237 var linesWrap = g.select('.nv-linesWrap')
6238 .datum(data.filter(function(d) { return !d.disabled }));
6240 linesWrap.call(lines);
6246 ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
6247 .tickSize(-availableHeight, 0);
6249 g.select('.nv-x.nv-axis')
6250 .attr('transform', 'translate(0,' + y.range()[0] + ')');
6251 g.select('.nv-x.nv-axis')
6258 ._ticks(nv.utils.calcTicksY(availableHeight/36, data) )
6259 .tickSize( -availableWidth, 0);
6261 g.select('.nv-y.nv-axis')
6265 //============================================================
6266 // Event Handling/Dispatching (in chart's scope)
6267 //------------------------------------------------------------
6269 legend.dispatch.on('stateChange', function(newState) {
6270 for (var key in newState)
6271 state[key] = newState[key];
6272 dispatch.stateChange(state);
6276 interactiveLayer.dispatch.on('elementMousemove', function(e) {
6277 lines.clearHighlights();
6278 var singlePoint, pointIndex, pointXLocation, allData = [];
6280 .filter(function(series, i) {
6281 series.seriesIndex = i;
6282 return !series.disabled;
6284 .forEach(function(series,i) {
6285 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
6286 var point = series.values[pointIndex];
6287 var pointYValue = chart.y()(point, pointIndex);
6288 if (pointYValue != null) {
6289 lines.highlightPoint(i, pointIndex, true);
6291 if (point === undefined) return;
6292 if (singlePoint === undefined) singlePoint = point;
6293 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
6297 color: color(series,series.seriesIndex)
6300 //Highlight the tooltip entry based on which point the mouse is closest to.
6301 if (allData.length > 2) {
6302 var yValue = chart.yScale().invert(e.mouseY);
6303 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
6304 var threshold = 0.03 * domainExtent;
6305 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
6306 if (indexToHighlight !== null)
6307 allData[indexToHighlight].highlight = true;
6310 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
6311 interactiveLayer.tooltip
6312 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
6313 .chartContainer(that.parentNode)
6314 .valueFormatter(function(d,i) {
6315 return d == null ? "N/A" : yAxis.tickFormat()(d);
6323 interactiveLayer.renderGuideLine(pointXLocation);
6327 interactiveLayer.dispatch.on('elementClick', function(e) {
6328 var pointXLocation, allData = [];
6330 data.filter(function(series, i) {
6331 series.seriesIndex = i;
6332 return !series.disabled;
6333 }).forEach(function(series) {
6334 var pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
6335 var point = series.values[pointIndex];
6336 if (typeof point === 'undefined') return;
6337 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
6338 var yPos = chart.yScale()(chart.y()(point,pointIndex));
6341 pointIndex: pointIndex,
6342 pos: [pointXLocation, yPos],
6343 seriesIndex: series.seriesIndex,
6348 lines.dispatch.elementClick(allData);
6351 interactiveLayer.dispatch.on("elementMouseout",function(e) {
6352 lines.clearHighlights();
6355 dispatch.on('changeState', function(e) {
6356 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
6357 data.forEach(function(series,i) {
6358 series.disabled = e.disabled[i];
6361 state.disabled = e.disabled;
6369 renderWatch.renderEnd('lineChart immediate');
6373 //============================================================
6374 // Event Handling/Dispatching (out of chart's scope)
6375 //------------------------------------------------------------
6377 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
6378 tooltip.data(evt).position(evt.pos).hidden(false);
6381 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6382 tooltip.hidden(true)
6385 //============================================================
6386 // Expose Public Variables
6387 //------------------------------------------------------------
6389 // expose chart's sub-components
6390 chart.dispatch = dispatch;
6391 chart.lines = lines;
6392 chart.legend = legend;
6393 chart.xAxis = xAxis;
6394 chart.yAxis = yAxis;
6395 chart.interactiveLayer = interactiveLayer;
6396 chart.tooltip = tooltip;
6398 chart.dispatch = dispatch;
6399 chart.options = nv.utils.optionsFunc.bind(chart);
6401 chart._options = Object.create({}, {
6402 // simple options, just get/set the necessary values
6403 width: {get: function(){return width;}, set: function(_){width=_;}},
6404 height: {get: function(){return height;}, set: function(_){height=_;}},
6405 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
6406 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
6407 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
6408 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
6409 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
6411 // deprecated options
6412 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
6413 // deprecated after 1.7.1
6414 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
6415 tooltip.enabled(!!_);
6417 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
6418 // deprecated after 1.7.1
6419 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
6420 tooltip.contentGenerator(_);
6423 // options that require extra logic in the setter
6424 margin: {get: function(){return margin;}, set: function(_){
6425 margin.top = _.top !== undefined ? _.top : margin.top;
6426 margin.right = _.right !== undefined ? _.right : margin.right;
6427 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
6428 margin.left = _.left !== undefined ? _.left : margin.left;
6430 duration: {get: function(){return duration;}, set: function(_){
6432 renderWatch.reset(duration);
6433 lines.duration(duration);
6434 xAxis.duration(duration);
6435 yAxis.duration(duration);
6437 color: {get: function(){return color;}, set: function(_){
6438 color = nv.utils.getColor(_);
6439 legend.color(color);
6442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
6443 rightAlignYAxis = _;
6444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
6446 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
6447 useInteractiveGuideline = _;
6448 if (useInteractiveGuideline) {
6449 lines.interactive(false);
6450 lines.useVoronoi(false);
6455 nv.utils.inheritOptions(chart, lines);
6456 nv.utils.initOptions(chart);
6460 nv.models.linePlusBarChart = function() {
6463 //============================================================
6464 // Public Variables with Default Settings
6465 //------------------------------------------------------------
6467 var lines = nv.models.line()
6468 , lines2 = nv.models.line()
6469 , bars = nv.models.historicalBar()
6470 , bars2 = nv.models.historicalBar()
6471 , xAxis = nv.models.axis()
6472 , x2Axis = nv.models.axis()
6473 , y1Axis = nv.models.axis()
6474 , y2Axis = nv.models.axis()
6475 , y3Axis = nv.models.axis()
6476 , y4Axis = nv.models.axis()
6477 , legend = nv.models.legend()
6478 , brush = d3.svg.brush()
6479 , tooltip = nv.models.tooltip()
6482 var margin = {top: 30, right: 30, bottom: 30, left: 60}
6483 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6486 , getX = function(d) { return d.x }
6487 , getY = function(d) { return d.y }
6488 , color = nv.utils.defaultColor()
6490 , focusEnable = true
6491 , focusShowAxisY = false
6492 , focusShowAxisX = true
6495 , brushExtent = null
6503 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
6504 , transitionDuration = 0
6505 , state = nv.utils.state()
6506 , defaultState = null
6507 , legendLeftAxisHint = ' (left axis)'
6508 , legendRightAxisHint = ' (right axis)'
6511 lines.clipEdge(true);
6512 lines2.interactive(false);
6513 xAxis.orient('bottom').tickPadding(5);
6514 y1Axis.orient('left');
6515 y2Axis.orient('right');
6516 x2Axis.orient('bottom').tickPadding(5);
6517 y3Axis.orient('left');
6518 y4Axis.orient('right');
6520 tooltip.headerEnabled(true).headerFormatter(function(d, i) {
6521 return xAxis.tickFormat()(d, i);
6524 //============================================================
6525 // Private Variables
6526 //------------------------------------------------------------
6528 var stateGetter = function(data) {
6531 active: data.map(function(d) { return !d.disabled })
6536 var stateSetter = function(data) {
6537 return function(state) {
6538 if (state.active !== undefined)
6539 data.forEach(function(series,i) {
6540 series.disabled = !state.active[i];
6545 function chart(selection) {
6546 selection.each(function(data) {
6547 var container = d3.select(this),
6549 nv.utils.initSVG(container);
6550 var availableWidth = nv.utils.availableWidth(width, container, margin),
6551 availableHeight1 = nv.utils.availableHeight(height, container, margin)
6552 - (focusEnable ? focusHeight : 0),
6553 availableHeight2 = focusHeight - margin2.top - margin2.bottom;
6555 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6556 chart.container = this;
6559 .setter(stateSetter(data), chart.update)
6560 .getter(stateGetter(data))
6563 // DEPRECATED set state.disableddisabled
6564 state.disabled = data.map(function(d) { return !!d.disabled });
6566 if (!defaultState) {
6569 for (key in state) {
6570 if (state[key] instanceof Array)
6571 defaultState[key] = state[key].slice(0);
6573 defaultState[key] = state[key];
6577 // Display No Data message if there's nothing to show.
6578 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6579 nv.utils.noData(chart, container)
6582 container.selectAll('.nv-noData').remove();
6586 var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6587 var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6590 x2 = x2Axis.scale();
6592 y2 = lines.yScale();
6593 y3 = bars2.yScale();
6594 y4 = lines2.yScale();
6597 .filter(function(d) { return !d.disabled && d.bar })
6599 return d.values.map(function(d,i) {
6600 return { x: getX(d,i), y: getY(d,i) }
6605 .filter(function(d) { return !d.disabled && !d.bar })
6607 return d.values.map(function(d,i) {
6608 return { x: getX(d,i), y: getY(d,i) }
6612 x.range([0, availableWidth]);
6614 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6615 .range([0, availableWidth]);
6617 // Setup containers and skeleton of chart
6618 var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6619 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6620 var g = wrap.select('g');
6622 gEnter.append('g').attr('class', 'nv-legendWrap');
6624 // this is the main chart
6625 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6626 focusEnter.append('g').attr('class', 'nv-x nv-axis');
6627 focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6628 focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6629 focusEnter.append('g').attr('class', 'nv-barsWrap');
6630 focusEnter.append('g').attr('class', 'nv-linesWrap');
6632 // context chart is where you can focus in
6633 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6634 contextEnter.append('g').attr('class', 'nv-x nv-axis');
6635 contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6636 contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6637 contextEnter.append('g').attr('class', 'nv-barsWrap');
6638 contextEnter.append('g').attr('class', 'nv-linesWrap');
6639 contextEnter.append('g').attr('class', 'nv-brushBackground');
6640 contextEnter.append('g').attr('class', 'nv-x nv-brush');
6642 //============================================================
6644 //------------------------------------------------------------
6647 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
6648 var legendXPosition = legend.align() ? legendWidth : 0;
6650 legend.width(legendWidth);
6652 g.select('.nv-legendWrap')
6653 .datum(data.map(function(series) {
6654 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6655 series.key = series.originalKey + (series.bar ? legendLeftAxisHint : legendRightAxisHint);
6660 if ( margin.top != legend.height()) {
6661 margin.top = legend.height();
6662 // FIXME: shouldn't this be "- (focusEnabled ? focusHeight : 0)"?
6663 availableHeight1 = nv.utils.availableHeight(height, container, margin) - focusHeight;
6666 g.select('.nv-legendWrap')
6667 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
6670 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6672 //============================================================
6673 // Context chart (focus chart) components
6674 //------------------------------------------------------------
6676 // hide or show the focus context chart
6677 g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
6680 .width(availableWidth)
6681 .height(availableHeight2)
6682 .color(data.map(function (d, i) {
6683 return d.color || color(d, i);
6684 }).filter(function (d, i) {
6685 return !data[i].disabled && data[i].bar
6688 .width(availableWidth)
6689 .height(availableHeight2)
6690 .color(data.map(function (d, i) {
6691 return d.color || color(d, i);
6692 }).filter(function (d, i) {
6693 return !data[i].disabled && !data[i].bar
6696 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6697 .datum(dataBars.length ? dataBars : [
6700 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6701 .datum(!dataLines[0].disabled ? dataLines : [
6705 g.select('.nv-context')
6706 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
6708 bars2Wrap.transition().call(bars2);
6709 lines2Wrap.transition().call(lines2);
6711 // context (focus chart) axis controls
6712 if (focusShowAxisX) {
6714 ._ticks( nv.utils.calcTicksX(availableWidth / 100, data))
6715 .tickSize(-availableHeight2, 0);
6716 g.select('.nv-context .nv-x.nv-axis')
6717 .attr('transform', 'translate(0,' + y3.range()[0] + ')');
6718 g.select('.nv-context .nv-x.nv-axis').transition()
6722 if (focusShowAxisY) {
6725 ._ticks( availableHeight2 / 36 )
6726 .tickSize( -availableWidth, 0);
6729 ._ticks( availableHeight2 / 36 )
6730 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6732 g.select('.nv-context .nv-y3.nv-axis')
6733 .style('opacity', dataBars.length ? 1 : 0)
6734 .attr('transform', 'translate(0,' + x2.range()[0] + ')');
6735 g.select('.nv-context .nv-y2.nv-axis')
6736 .style('opacity', dataLines.length ? 1 : 0)
6737 .attr('transform', 'translate(' + x2.range()[1] + ',0)');
6739 g.select('.nv-context .nv-y1.nv-axis').transition()
6741 g.select('.nv-context .nv-y2.nv-axis').transition()
6746 brush.x(x2).on('brush', onBrush);
6748 if (brushExtent) brush.extent(brushExtent);
6750 var brushBG = g.select('.nv-brushBackground').selectAll('g')
6751 .data([brushExtent || brush.extent()]);
6753 var brushBGenter = brushBG.enter()
6756 brushBGenter.append('rect')
6757 .attr('class', 'left')
6760 .attr('height', availableHeight2);
6762 brushBGenter.append('rect')
6763 .attr('class', 'right')
6766 .attr('height', availableHeight2);
6768 var gBrush = g.select('.nv-x.nv-brush')
6770 gBrush.selectAll('rect')
6772 .attr('height', availableHeight2);
6773 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6775 //============================================================
6776 // Event Handling/Dispatching (in chart's scope)
6777 //------------------------------------------------------------
6779 legend.dispatch.on('stateChange', function(newState) {
6780 for (var key in newState)
6781 state[key] = newState[key];
6782 dispatch.stateChange(state);
6786 // Update chart from a state object passed to event handler
6787 dispatch.on('changeState', function(e) {
6788 if (typeof e.disabled !== 'undefined') {
6789 data.forEach(function(series,i) {
6790 series.disabled = e.disabled[i];
6792 state.disabled = e.disabled;
6797 //============================================================
6799 //------------------------------------------------------------
6801 // Taken from crossfilter (http://square.github.com/crossfilter/)
6802 function resizePath(d) {
6803 var e = +(d == 'e'),
6805 y = availableHeight2 / 3;
6806 return 'M' + (.5 * x) + ',' + y
6807 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6809 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6811 + 'M' + (2.5 * x) + ',' + (y + 8)
6813 + 'M' + (4.5 * x) + ',' + (y + 8)
6814 + 'V' + (2 * y - 8);
6818 function updateBrushBG() {
6819 if (!brush.empty()) brush.extent(brushExtent);
6821 .data([brush.empty() ? x2.domain() : brushExtent])
6822 .each(function(d,i) {
6823 var leftWidth = x2(d[0]) - x2.range()[0],
6824 rightWidth = x2.range()[1] - x2(d[1]);
6825 d3.select(this).select('.left')
6826 .attr('width', leftWidth < 0 ? 0 : leftWidth);
6828 d3.select(this).select('.right')
6829 .attr('x', x2(d[1]))
6830 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6834 function onBrush() {
6835 brushExtent = brush.empty() ? null : brush.extent();
6836 extent = brush.empty() ? x2.domain() : brush.extent();
6837 dispatch.brush({extent: extent, brush: brush});
6840 // Prepare Main (Focus) Bars and Lines
6842 .width(availableWidth)
6843 .height(availableHeight1)
6844 .color(data.map(function(d,i) {
6845 return d.color || color(d, i);
6846 }).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
6849 .width(availableWidth)
6850 .height(availableHeight1)
6851 .color(data.map(function(d,i) {
6852 return d.color || color(d, i);
6853 }).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
6855 var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
6856 .datum(!dataBars.length ? [{values:[]}] :
6858 .map(function(d,i) {
6861 values: d.values.filter(function(d,i) {
6862 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
6868 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6869 .datum(dataLines[0].disabled ? [{values:[]}] :
6871 .map(function(d,i) {
6874 fillOpacity: d.fillOpacity,
6876 values: d.values.filter(function(d,i) {
6877 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6883 // Update Main (Focus) X Axis
6884 if (dataBars.length) {
6892 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6893 .tickSize(-availableHeight1, 0);
6895 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
6897 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
6900 // Update Main (Focus) Bars and Lines
6901 focusBarsWrap.transition().duration(transitionDuration).call(bars);
6902 focusLinesWrap.transition().duration(transitionDuration).call(lines);
6904 // Setup and Update Main (Focus) Y Axes
6905 g.select('.nv-focus .nv-x.nv-axis')
6906 .attr('transform', 'translate(0,' + y1.range()[0] + ')');
6910 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
6911 .tickSize(-availableWidth, 0);
6914 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
6915 .tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6917 g.select('.nv-focus .nv-y1.nv-axis')
6918 .style('opacity', dataBars.length ? 1 : 0);
6919 g.select('.nv-focus .nv-y2.nv-axis')
6920 .style('opacity', dataLines.length && !dataLines[0].disabled ? 1 : 0)
6921 .attr('transform', 'translate(' + x.range()[1] + ',0)');
6923 g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
6925 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
6936 //============================================================
6937 // Event Handling/Dispatching (out of chart's scope)
6938 //------------------------------------------------------------
6940 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
6943 .valueFormatter(function(d, i) {
6944 return y2Axis.tickFormat()(d, i);
6951 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6952 tooltip.hidden(true)
6955 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
6956 evt.value = chart.x()(evt.data);
6958 value: chart.y()(evt.data),
6963 .valueFormatter(function(d, i) {
6964 return y1Axis.tickFormat()(d, i);
6970 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
6971 tooltip.hidden(true);
6974 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
6975 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
6978 //============================================================
6981 //============================================================
6982 // Expose Public Variables
6983 //------------------------------------------------------------
6985 // expose chart's sub-components
6986 chart.dispatch = dispatch;
6987 chart.legend = legend;
6988 chart.lines = lines;
6989 chart.lines2 = lines2;
6991 chart.bars2 = bars2;
6992 chart.xAxis = xAxis;
6993 chart.x2Axis = x2Axis;
6994 chart.y1Axis = y1Axis;
6995 chart.y2Axis = y2Axis;
6996 chart.y3Axis = y3Axis;
6997 chart.y4Axis = y4Axis;
6998 chart.tooltip = tooltip;
7000 chart.options = nv.utils.optionsFunc.bind(chart);
7002 chart._options = Object.create({}, {
7003 // simple options, just get/set the necessary values
7004 width: {get: function(){return width;}, set: function(_){width=_;}},
7005 height: {get: function(){return height;}, set: function(_){height=_;}},
7006 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
7007 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
7008 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
7009 focusEnable: {get: function(){return focusEnable;}, set: function(_){focusEnable=_;}},
7010 focusHeight: {get: function(){return focusHeight;}, set: function(_){focusHeight=_;}},
7011 focusShowAxisX: {get: function(){return focusShowAxisX;}, set: function(_){focusShowAxisX=_;}},
7012 focusShowAxisY: {get: function(){return focusShowAxisY;}, set: function(_){focusShowAxisY=_;}},
7013 legendLeftAxisHint: {get: function(){return legendLeftAxisHint;}, set: function(_){legendLeftAxisHint=_;}},
7014 legendRightAxisHint: {get: function(){return legendRightAxisHint;}, set: function(_){legendRightAxisHint=_;}},
7016 // deprecated options
7017 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
7018 // deprecated after 1.7.1
7019 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
7020 tooltip.enabled(!!_);
7022 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
7023 // deprecated after 1.7.1
7024 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
7025 tooltip.contentGenerator(_);
7028 // options that require extra logic in the setter
7029 margin: {get: function(){return margin;}, set: function(_){
7030 margin.top = _.top !== undefined ? _.top : margin.top;
7031 margin.right = _.right !== undefined ? _.right : margin.right;
7032 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7033 margin.left = _.left !== undefined ? _.left : margin.left;
7035 duration: {get: function(){return transitionDuration;}, set: function(_){
7036 transitionDuration = _;
7038 color: {get: function(){return color;}, set: function(_){
7039 color = nv.utils.getColor(_);
7040 legend.color(color);
7042 x: {get: function(){return getX;}, set: function(_){
7049 y: {get: function(){return getY;}, set: function(_){
7058 nv.utils.inheritOptions(chart, lines);
7059 nv.utils.initOptions(chart);
7063 nv.models.lineWithFocusChart = function() {
7066 //============================================================
7067 // Public Variables with Default Settings
7068 //------------------------------------------------------------
7070 var lines = nv.models.line()
7071 , lines2 = nv.models.line()
7072 , xAxis = nv.models.axis()
7073 , yAxis = nv.models.axis()
7074 , x2Axis = nv.models.axis()
7075 , y2Axis = nv.models.axis()
7076 , legend = nv.models.legend()
7077 , brush = d3.svg.brush()
7078 , tooltip = nv.models.tooltip()
7079 , interactiveLayer = nv.interactiveGuideline()
7082 var margin = {top: 30, right: 30, bottom: 30, left: 60}
7083 , margin2 = {top: 0, right: 30, bottom: 20, left: 60}
7084 , color = nv.utils.defaultColor()
7088 , useInteractiveGuideline = false
7094 , brushExtent = null
7096 , dispatch = d3.dispatch('brush', 'stateChange', 'changeState')
7097 , transitionDuration = 250
7098 , state = nv.utils.state()
7099 , defaultState = null
7102 lines.clipEdge(true).duration(0);
7103 lines2.interactive(false);
7104 xAxis.orient('bottom').tickPadding(5);
7105 yAxis.orient('left');
7106 x2Axis.orient('bottom').tickPadding(5);
7107 y2Axis.orient('left');
7109 tooltip.valueFormatter(function(d, i) {
7110 return yAxis.tickFormat()(d, i);
7111 }).headerFormatter(function(d, i) {
7112 return xAxis.tickFormat()(d, i);
7115 //============================================================
7116 // Private Variables
7117 //------------------------------------------------------------
7119 var stateGetter = function(data) {
7122 active: data.map(function(d) { return !d.disabled })
7127 var stateSetter = function(data) {
7128 return function(state) {
7129 if (state.active !== undefined)
7130 data.forEach(function(series,i) {
7131 series.disabled = !state.active[i];
7136 function chart(selection) {
7137 selection.each(function(data) {
7138 var container = d3.select(this),
7140 nv.utils.initSVG(container);
7141 var availableWidth = nv.utils.availableWidth(width, container, margin),
7142 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2,
7143 availableHeight2 = height2 - margin2.top - margin2.bottom;
7145 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7146 chart.container = this;
7149 .setter(stateSetter(data), chart.update)
7150 .getter(stateGetter(data))
7153 // DEPRECATED set state.disableddisabled
7154 state.disabled = data.map(function(d) { return !!d.disabled });
7156 if (!defaultState) {
7159 for (key in state) {
7160 if (state[key] instanceof Array)
7161 defaultState[key] = state[key].slice(0);
7163 defaultState[key] = state[key];
7167 // Display No Data message if there's nothing to show.
7168 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7169 nv.utils.noData(chart, container)
7172 container.selectAll('.nv-noData').remove();
7178 x2 = lines2.xScale();
7179 y2 = lines2.yScale();
7181 // Setup containers and skeleton of chart
7182 var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
7183 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
7184 var g = wrap.select('g');
7186 gEnter.append('g').attr('class', 'nv-legendWrap');
7188 var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
7189 focusEnter.append('g').attr('class', 'nv-x nv-axis');
7190 focusEnter.append('g').attr('class', 'nv-y nv-axis');
7191 focusEnter.append('g').attr('class', 'nv-linesWrap');
7192 focusEnter.append('g').attr('class', 'nv-interactive');
7194 var contextEnter = gEnter.append('g').attr('class', 'nv-context');
7195 contextEnter.append('g').attr('class', 'nv-x nv-axis');
7196 contextEnter.append('g').attr('class', 'nv-y nv-axis');
7197 contextEnter.append('g').attr('class', 'nv-linesWrap');
7198 contextEnter.append('g').attr('class', 'nv-brushBackground');
7199 contextEnter.append('g').attr('class', 'nv-x nv-brush');
7203 legend.width(availableWidth);
7205 g.select('.nv-legendWrap')
7209 if ( margin.top != legend.height()) {
7210 margin.top = legend.height();
7211 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2;
7214 g.select('.nv-legendWrap')
7215 .attr('transform', 'translate(0,' + (-margin.top) +')')
7218 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7221 //Set up interactive layer
7222 if (useInteractiveGuideline) {
7224 .width(availableWidth)
7225 .height(availableHeight1)
7226 .margin({left:margin.left, top:margin.top})
7227 .svgContainer(container)
7229 wrap.select(".nv-interactive").call(interactiveLayer);
7232 // Main Chart Component(s)
7234 .width(availableWidth)
7235 .height(availableHeight1)
7238 .map(function(d,i) {
7239 return d.color || color(d, i);
7241 .filter(function(d,i) {
7242 return !data[i].disabled;
7247 .defined(lines.defined())
7248 .width(availableWidth)
7249 .height(availableHeight2)
7252 .map(function(d,i) {
7253 return d.color || color(d, i);
7255 .filter(function(d,i) {
7256 return !data[i].disabled;
7260 g.select('.nv-context')
7261 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7263 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
7264 .datum(data.filter(function(d) { return !d.disabled }))
7266 d3.transition(contextLinesWrap).call(lines2);
7268 // Setup Main (Focus) Axes
7271 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7272 .tickSize(-availableHeight1, 0);
7276 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7277 .tickSize( -availableWidth, 0);
7279 g.select('.nv-focus .nv-x.nv-axis')
7280 .attr('transform', 'translate(0,' + availableHeight1 + ')');
7285 .on('brush', function() {
7289 if (brushExtent) brush.extent(brushExtent);
7291 var brushBG = g.select('.nv-brushBackground').selectAll('g')
7292 .data([brushExtent || brush.extent()])
7294 var brushBGenter = brushBG.enter()
7297 brushBGenter.append('rect')
7298 .attr('class', 'left')
7301 .attr('height', availableHeight2);
7303 brushBGenter.append('rect')
7304 .attr('class', 'right')
7307 .attr('height', availableHeight2);
7309 var gBrush = g.select('.nv-x.nv-brush')
7311 gBrush.selectAll('rect')
7312 .attr('height', availableHeight2);
7313 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7317 // Setup Secondary (Context) Axes
7320 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7321 .tickSize(-availableHeight2, 0);
7323 g.select('.nv-context .nv-x.nv-axis')
7324 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7325 d3.transition(g.select('.nv-context .nv-x.nv-axis'))
7330 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
7331 .tickSize( -availableWidth, 0);
7333 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
7336 g.select('.nv-context .nv-x.nv-axis')
7337 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7339 //============================================================
7340 // Event Handling/Dispatching (in chart's scope)
7341 //------------------------------------------------------------
7343 legend.dispatch.on('stateChange', function(newState) {
7344 for (var key in newState)
7345 state[key] = newState[key];
7346 dispatch.stateChange(state);
7350 interactiveLayer.dispatch.on('elementMousemove', function(e) {
7351 lines.clearHighlights();
7352 var singlePoint, pointIndex, pointXLocation, allData = [];
7354 .filter(function(series, i) {
7355 series.seriesIndex = i;
7356 return !series.disabled;
7358 .forEach(function(series,i) {
7359 var extent = brush.empty() ? x2.domain() : brush.extent();
7360 var currentValues = series.values.filter(function(d,i) {
7361 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7364 pointIndex = nv.interactiveBisect(currentValues, e.pointXValue, lines.x());
7365 var point = currentValues[pointIndex];
7366 var pointYValue = chart.y()(point, pointIndex);
7367 if (pointYValue != null) {
7368 lines.highlightPoint(i, pointIndex, true);
7370 if (point === undefined) return;
7371 if (singlePoint === undefined) singlePoint = point;
7372 if (pointXLocation === undefined) pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
7375 value: chart.y()(point, pointIndex),
7376 color: color(series,series.seriesIndex)
7379 //Highlight the tooltip entry based on which point the mouse is closest to.
7380 if (allData.length > 2) {
7381 var yValue = chart.yScale().invert(e.mouseY);
7382 var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
7383 var threshold = 0.03 * domainExtent;
7384 var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
7385 if (indexToHighlight !== null)
7386 allData[indexToHighlight].highlight = true;
7389 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
7390 interactiveLayer.tooltip
7391 .position({left: e.mouseX + margin.left, top: e.mouseY + margin.top})
7392 .chartContainer(that.parentNode)
7393 .valueFormatter(function(d,i) {
7394 return d == null ? "N/A" : yAxis.tickFormat()(d);
7402 interactiveLayer.renderGuideLine(pointXLocation);
7406 interactiveLayer.dispatch.on("elementMouseout",function(e) {
7407 lines.clearHighlights();
7410 dispatch.on('changeState', function(e) {
7411 if (typeof e.disabled !== 'undefined') {
7412 data.forEach(function(series,i) {
7413 series.disabled = e.disabled[i];
7419 //============================================================
7421 //------------------------------------------------------------
7423 // Taken from crossfilter (http://square.github.com/crossfilter/)
7424 function resizePath(d) {
7425 var e = +(d == 'e'),
7427 y = availableHeight2 / 3;
7428 return 'M' + (.5 * x) + ',' + y
7429 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7431 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7433 + 'M' + (2.5 * x) + ',' + (y + 8)
7435 + 'M' + (4.5 * x) + ',' + (y + 8)
7436 + 'V' + (2 * y - 8);
7440 function updateBrushBG() {
7441 if (!brush.empty()) brush.extent(brushExtent);
7443 .data([brush.empty() ? x2.domain() : brushExtent])
7444 .each(function(d,i) {
7445 var leftWidth = x2(d[0]) - x.range()[0],
7446 rightWidth = availableWidth - x2(d[1]);
7447 d3.select(this).select('.left')
7448 .attr('width', leftWidth < 0 ? 0 : leftWidth);
7450 d3.select(this).select('.right')
7451 .attr('x', x2(d[1]))
7452 .attr('width', rightWidth < 0 ? 0 : rightWidth);
7457 function onBrush() {
7458 brushExtent = brush.empty() ? null : brush.extent();
7459 var extent = brush.empty() ? x2.domain() : brush.extent();
7461 //The brush extent cannot be less than one. If it is, don't update the line chart.
7462 if (Math.abs(extent[0] - extent[1]) <= 1) {
7466 dispatch.brush({extent: extent, brush: brush});
7471 // Update Main (Focus)
7472 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7475 .filter(function(d) { return !d.disabled })
7476 .map(function(d,i) {
7480 values: d.values.filter(function(d,i) {
7481 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7486 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7489 // Update Main (Focus) Axes
7490 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
7492 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
7500 //============================================================
7501 // Event Handling/Dispatching (out of chart's scope)
7502 //------------------------------------------------------------
7504 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
7505 tooltip.data(evt).position(evt.pos).hidden(false);
7508 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
7509 tooltip.hidden(true)
7512 //============================================================
7513 // Expose Public Variables
7514 //------------------------------------------------------------
7516 // expose chart's sub-components
7517 chart.dispatch = dispatch;
7518 chart.legend = legend;
7519 chart.lines = lines;
7520 chart.lines2 = lines2;
7521 chart.xAxis = xAxis;
7522 chart.yAxis = yAxis;
7523 chart.x2Axis = x2Axis;
7524 chart.y2Axis = y2Axis;
7525 chart.interactiveLayer = interactiveLayer;
7526 chart.tooltip = tooltip;
7528 chart.options = nv.utils.optionsFunc.bind(chart);
7530 chart._options = Object.create({}, {
7531 // simple options, just get/set the necessary values
7532 width: {get: function(){return width;}, set: function(_){width=_;}},
7533 height: {get: function(){return height;}, set: function(_){height=_;}},
7534 focusHeight: {get: function(){return height2;}, set: function(_){height2=_;}},
7535 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
7536 brushExtent: {get: function(){return brushExtent;}, set: function(_){brushExtent=_;}},
7537 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
7538 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
7540 // deprecated options
7541 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
7542 // deprecated after 1.7.1
7543 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
7544 tooltip.enabled(!!_);
7546 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
7547 // deprecated after 1.7.1
7548 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
7549 tooltip.contentGenerator(_);
7552 // options that require extra logic in the setter
7553 margin: {get: function(){return margin;}, set: function(_){
7554 margin.top = _.top !== undefined ? _.top : margin.top;
7555 margin.right = _.right !== undefined ? _.right : margin.right;
7556 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
7557 margin.left = _.left !== undefined ? _.left : margin.left;
7559 color: {get: function(){return color;}, set: function(_){
7560 color = nv.utils.getColor(_);
7561 legend.color(color);
7562 // line color is handled above?
7564 interpolate: {get: function(){return lines.interpolate();}, set: function(_){
7565 lines.interpolate(_);
7566 lines2.interpolate(_);
7568 xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
7569 xAxis.tickFormat(_);
7570 x2Axis.tickFormat(_);
7572 yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
7573 yAxis.tickFormat(_);
7574 y2Axis.tickFormat(_);
7576 duration: {get: function(){return transitionDuration;}, set: function(_){
7577 transitionDuration=_;
7578 yAxis.duration(transitionDuration);
7579 y2Axis.duration(transitionDuration);
7580 xAxis.duration(transitionDuration);
7581 x2Axis.duration(transitionDuration);
7583 x: {get: function(){return lines.x();}, set: function(_){
7587 y: {get: function(){return lines.y();}, set: function(_){
7591 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
7592 useInteractiveGuideline = _;
7593 if (useInteractiveGuideline) {
7594 lines.interactive(false);
7595 lines.useVoronoi(false);
7600 nv.utils.inheritOptions(chart, lines);
7601 nv.utils.initOptions(chart);
7606 nv.models.multiBar = function() {
7609 //============================================================
7610 // Public Variables with Default Settings
7611 //------------------------------------------------------------
7613 var margin = {top: 0, right: 0, bottom: 0, left: 0}
7616 , x = d3.scale.ordinal()
7617 , y = d3.scale.linear()
7618 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7620 , getX = function(d) { return d.x }
7621 , getY = function(d) { return d.y }
7622 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7625 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
7626 , color = nv.utils.defaultColor()
7628 , barColor = null // adding the ability to set the color for each rather than the whole group
7629 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7635 , groupSpacing = 0.1
7636 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
7639 //============================================================
7640 // Private Variables
7641 //------------------------------------------------------------
7643 var x0, y0 //used to store previous scales
7644 , renderWatch = nv.utils.renderWatch(dispatch, duration)
7647 var last_datalength = 0;
7649 function chart(selection) {
7650 renderWatch.reset();
7651 selection.each(function(data) {
7652 var availableWidth = width - margin.left - margin.right,
7653 availableHeight = height - margin.top - margin.bottom;
7655 container = d3.select(this);
7656 nv.utils.initSVG(container);
7657 var nonStackableCount = 0;
7658 // This function defines the requirements for render complete
7659 var endFn = function(d, i) {
7660 if (d.series === data.length - 1 && i === data[0].values.length - 1)
7665 if(hideable && data.length) hideable = [{
7666 values: data[0].values.map(function(d) {
7676 var parsed = d3.layout.stack()
7677 .offset(stackOffset)
7678 .values(function(d){ return d.values })
7680 (!data.length && hideable ? hideable : data);
7682 parsed.forEach(function(series, i){
7683 // if series is non-stackable, use un-parsed data
7684 if (series.nonStackable) {
7685 data[i].nonStackableSeries = nonStackableCount++;
7686 parsed[i] = data[i];
7688 // don't stack this seires on top of the nonStackable seriees
7689 if (i > 0 && parsed[i - 1].nonStackable){
7690 parsed[i].values.map(function(d,j){
7691 d.y0 -= parsed[i - 1].values[j].y;
7699 //add series index and key to each data point for reference
7700 data.forEach(function(series, i) {
7701 series.values.forEach(function(point) {
7703 point.key = series.key;
7707 // HACK for negative value stacking
7709 data[0].values.map(function(d,i) {
7710 var posBase = 0, negBase = 0;
7711 data.map(function(d, idx) {
7712 if (!data[idx].nonStackable) {
7714 f.size = Math.abs(f.y);
7717 negBase = negBase - f.size;
7720 f.y1 = f.size + posBase;
7721 posBase = posBase + f.size;
7729 // remap and flatten the data for use in calculating the scales' domains
7730 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7731 data.map(function(d, idx) {
7732 return d.values.map(function(d,i) {
7733 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1, idx:idx }
7737 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7738 .rangeBands(xRange || [0, availableWidth], groupSpacing);
7740 y.domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) {
7742 // increase the domain range if this series is stackable
7743 if (stacked && !data[d.idx].nonStackable) {
7752 .range(yRange || [availableHeight, 0]);
7754 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
7755 if (x.domain()[0] === x.domain()[1])
7757 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7760 if (y.domain()[0] === y.domain()[1])
7762 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7768 // Setup containers and skeleton of chart
7769 var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
7770 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
7771 var defsEnter = wrapEnter.append('defs');
7772 var gEnter = wrapEnter.append('g');
7773 var g = wrap.select('g');
7775 gEnter.append('g').attr('class', 'nv-groups');
7776 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7778 defsEnter.append('clipPath')
7779 .attr('id', 'nv-edge-clip-' + id)
7781 wrap.select('#nv-edge-clip-' + id + ' rect')
7782 .attr('width', availableWidth)
7783 .attr('height', availableHeight);
7785 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7787 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7788 .data(function(d) { return d }, function(d,i) { return i });
7789 groups.enter().append('g')
7790 .style('stroke-opacity', 1e-6)
7791 .style('fill-opacity', 1e-6);
7793 var exitTransition = renderWatch
7794 .transition(groups.exit().selectAll('rect.nv-bar'), 'multibarExit', Math.min(100, duration))
7795 .attr('y', function(d, i, j) {
7796 var yVal = y0(0) || 0;
7798 if (data[d.series] && !data[d.series].nonStackable) {
7806 if (exitTransition.delay)
7807 exitTransition.delay(function(d,i) {
7808 var delay = i * (duration / (last_datalength + 1)) - i;
7812 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7813 .classed('hover', function(d) { return d.hover })
7814 .style('fill', function(d,i){ return color(d, i) })
7815 .style('stroke', function(d,i){ return color(d, i) });
7817 .style('stroke-opacity', 1)
7818 .style('fill-opacity', 0.75);
7820 var bars = groups.selectAll('rect.nv-bar')
7821 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7822 bars.exit().remove();
7824 var barsEnter = bars.enter().append('rect')
7825 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7826 .attr('x', function(d,i,j) {
7827 return stacked && !data[j].nonStackable ? 0 : (j * x.rangeBand() / data.length )
7829 .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
7831 .attr('width', function(d,i,j) { return x.rangeBand() / (stacked && !data[j].nonStackable ? 1 : data.length) })
7832 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7835 .style('fill', function(d,i,j){ return color(d, j, i); })
7836 .style('stroke', function(d,i,j){ return color(d, j, i); })
7837 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7838 d3.select(this).classed('hover', true);
7839 dispatch.elementMouseover({
7842 color: d3.select(this).style("fill")
7845 .on('mouseout', function(d,i) {
7846 d3.select(this).classed('hover', false);
7847 dispatch.elementMouseout({
7850 color: d3.select(this).style("fill")
7853 .on('mousemove', function(d,i) {
7854 dispatch.elementMousemove({
7857 color: d3.select(this).style("fill")
7860 .on('click', function(d,i) {
7861 dispatch.elementClick({
7864 color: d3.select(this).style("fill")
7866 d3.event.stopPropagation();
7868 .on('dblclick', function(d,i) {
7869 dispatch.elementDblClick({
7872 color: d3.select(this).style("fill")
7874 d3.event.stopPropagation();
7877 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7878 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7881 if (!disabled) disabled = data.map(function() { return true });
7883 .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(); })
7884 .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(); });
7888 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
7889 .delay(function(d,i) {
7890 return i * duration / data[0].values.length;
7894 .attr('y', function(d,i,j) {
7896 // if stackable, stack it on top of the previous series
7897 if (!data[j].nonStackable) {
7903 if (y(0) - y(getY(d,i)) < -1){
7906 yVal = y(getY(d, i)) || 0;
7912 .attr('height', function(d,i,j) {
7913 if (!data[j].nonStackable) {
7914 return Math.max(Math.abs(y(d.y+d.y0) - y(d.y0)), 1);
7916 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7919 .attr('x', function(d,i,j) {
7921 if (data[j].nonStackable) {
7922 width = d.series * x.rangeBand() / data.length;
7923 if (data.length !== nonStackableCount){
7924 width = data[j].nonStackableSeries * x.rangeBand()/(nonStackableCount*2);
7929 .attr('width', function(d,i,j){
7930 if (!data[j].nonStackable) {
7931 return x.rangeBand();
7933 // if all series are nonStacable, take the full width
7934 var width = (x.rangeBand() / nonStackableCount);
7935 // otherwise, nonStackable graph will be only taking the half-width
7936 // of the x rangeBand
7937 if (data.length !== nonStackableCount) {
7938 width = x.rangeBand()/(nonStackableCount*2);
7946 .attr('x', function(d,i) {
7947 return d.series * x.rangeBand() / data.length;
7949 .attr('width', x.rangeBand() / data.length)
7950 .attr('y', function(d,i) {
7951 return getY(d,i) < 0 ?
7953 y(0) - y(getY(d,i)) < 1 ?
7957 .attr('height', function(d,i) {
7958 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7962 //store old scales for use in transitions on update
7966 // keep track of the last data value length for transition calculations
7967 if (data[0] && data[0].values) {
7968 last_datalength = data[0].values.length;
7973 renderWatch.renderEnd('multibar immediate');
7978 //============================================================
7979 // Expose Public Variables
7980 //------------------------------------------------------------
7982 chart.dispatch = dispatch;
7984 chart.options = nv.utils.optionsFunc.bind(chart);
7986 chart._options = Object.create({}, {
7987 // simple options, just get/set the necessary values
7988 width: {get: function(){return width;}, set: function(_){width=_;}},
7989 height: {get: function(){return height;}, set: function(_){height=_;}},
7990 x: {get: function(){return getX;}, set: function(_){getX=_;}},
7991 y: {get: function(){return getY;}, set: function(_){getY=_;}},
7992 xScale: {get: function(){return x;}, set: function(_){x=_;}},
7993 yScale: {get: function(){return y;}, set: function(_){y=_;}},
7994 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
7995 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
7996 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
7997 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
7998 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
7999 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
8000 stackOffset: {get: function(){return stackOffset;}, set: function(_){stackOffset=_;}},
8001 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
8002 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
8003 id: {get: function(){return id;}, set: function(_){id=_;}},
8004 hideable: {get: function(){return hideable;}, set: function(_){hideable=_;}},
8005 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
8007 // options that require extra logic in the setter
8008 margin: {get: function(){return margin;}, set: function(_){
8009 margin.top = _.top !== undefined ? _.top : margin.top;
8010 margin.right = _.right !== undefined ? _.right : margin.right;
8011 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8012 margin.left = _.left !== undefined ? _.left : margin.left;
8014 duration: {get: function(){return duration;}, set: function(_){
8016 renderWatch.reset(duration);
8018 color: {get: function(){return color;}, set: function(_){
8019 color = nv.utils.getColor(_);
8021 barColor: {get: function(){return barColor;}, set: function(_){
8022 barColor = _ ? nv.utils.getColor(_) : null;
8026 nv.utils.initOptions(chart);
8030 nv.models.multiBarChart = function() {
8033 //============================================================
8034 // Public Variables with Default Settings
8035 //------------------------------------------------------------
8037 var multibar = nv.models.multiBar()
8038 , xAxis = nv.models.axis()
8039 , yAxis = nv.models.axis()
8040 , legend = nv.models.legend()
8041 , controls = nv.models.legend()
8042 , tooltip = nv.models.tooltip()
8045 var margin = {top: 30, right: 20, bottom: 50, left: 60}
8048 , color = nv.utils.defaultColor()
8049 , showControls = true
8050 , controlLabels = {}
8054 , rightAlignYAxis = false
8055 , reduceXTicks = true // if false a tick will show for every data point
8056 , staggerLabels = false
8058 , x //can be accessed via chart.xScale()
8059 , y //can be accessed via chart.yScale()
8060 , state = nv.utils.state()
8061 , defaultState = null
8063 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
8064 , controlWidth = function() { return showControls ? 180 : 0 }
8068 state.stacked = false // DEPRECATED Maintained for backward compatibility
8070 multibar.stacked(false);
8075 .tickFormat(function(d) { return d })
8078 .orient((rightAlignYAxis) ? 'right' : 'left')
8079 .tickFormat(d3.format(',.1f'))
8084 .valueFormatter(function(d, i) {
8085 return yAxis.tickFormat()(d, i);
8087 .headerFormatter(function(d, i) {
8088 return xAxis.tickFormat()(d, i);
8091 controls.updateState(false);
8093 //============================================================
8094 // Private Variables
8095 //------------------------------------------------------------
8097 var renderWatch = nv.utils.renderWatch(dispatch);
8098 var stacked = false;
8100 var stateGetter = function(data) {
8103 active: data.map(function(d) { return !d.disabled }),
8109 var stateSetter = function(data) {
8110 return function(state) {
8111 if (state.stacked !== undefined)
8112 stacked = state.stacked;
8113 if (state.active !== undefined)
8114 data.forEach(function(series,i) {
8115 series.disabled = !state.active[i];
8120 function chart(selection) {
8121 renderWatch.reset();
8122 renderWatch.models(multibar);
8123 if (showXAxis) renderWatch.models(xAxis);
8124 if (showYAxis) renderWatch.models(yAxis);
8126 selection.each(function(data) {
8127 var container = d3.select(this),
8129 nv.utils.initSVG(container);
8130 var availableWidth = nv.utils.availableWidth(width, container, margin),
8131 availableHeight = nv.utils.availableHeight(height, container, margin);
8133 chart.update = function() {
8135 container.call(chart);
8137 container.transition()
8141 chart.container = this;
8144 .setter(stateSetter(data), chart.update)
8145 .getter(stateGetter(data))
8148 // DEPRECATED set state.disableddisabled
8149 state.disabled = data.map(function(d) { return !!d.disabled });
8151 if (!defaultState) {
8154 for (key in state) {
8155 if (state[key] instanceof Array)
8156 defaultState[key] = state[key].slice(0);
8158 defaultState[key] = state[key];
8162 // Display noData message if there's nothing to show.
8163 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8164 nv.utils.noData(chart, container)
8167 container.selectAll('.nv-noData').remove();
8171 x = multibar.xScale();
8172 y = multibar.yScale();
8174 // Setup containers and skeleton of chart
8175 var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8176 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8177 var g = wrap.select('g');
8179 gEnter.append('g').attr('class', 'nv-x nv-axis');
8180 gEnter.append('g').attr('class', 'nv-y nv-axis');
8181 gEnter.append('g').attr('class', 'nv-barsWrap');
8182 gEnter.append('g').attr('class', 'nv-legendWrap');
8183 gEnter.append('g').attr('class', 'nv-controlsWrap');
8187 legend.width(availableWidth - controlWidth());
8189 g.select('.nv-legendWrap')
8193 if ( margin.top != legend.height()) {
8194 margin.top = legend.height();
8195 availableHeight = nv.utils.availableHeight(height, container, margin);
8198 g.select('.nv-legendWrap')
8199 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8204 var controlsData = [
8205 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8206 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
8209 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8210 g.select('.nv-controlsWrap')
8211 .datum(controlsData)
8212 .attr('transform', 'translate(0,' + (-margin.top) +')')
8216 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8217 if (rightAlignYAxis) {
8218 g.select(".nv-y.nv-axis")
8219 .attr("transform", "translate(" + availableWidth + ",0)");
8222 // Main Chart Component(s)
8224 .disabled(data.map(function(series) { return series.disabled }))
8225 .width(availableWidth)
8226 .height(availableHeight)
8227 .color(data.map(function(d,i) {
8228 return d.color || color(d, i);
8229 }).filter(function(d,i) { return !data[i].disabled }));
8232 var barsWrap = g.select('.nv-barsWrap')
8233 .datum(data.filter(function(d) { return !d.disabled }));
8235 barsWrap.call(multibar);
8241 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8242 .tickSize(-availableHeight, 0);
8244 g.select('.nv-x.nv-axis')
8245 .attr('transform', 'translate(0,' + y.range()[0] + ')');
8246 g.select('.nv-x.nv-axis')
8249 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8252 .selectAll('line, text')
8253 .style('opacity', 1)
8255 if (staggerLabels) {
8256 var getTranslate = function(x,y) {
8257 return "translate(" + x + "," + y + ")";
8260 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
8264 .attr('transform', function(d,i,j) {
8265 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8268 var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8269 g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8270 .attr("transform", function(d,i) {
8271 return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8277 .filter(function(d,i) {
8278 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8280 .selectAll('text, line')
8281 .style('opacity', 0);
8285 .selectAll('.tick text')
8286 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8287 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8289 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8290 .style('opacity', 1);
8296 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8297 .tickSize( -availableWidth, 0);
8299 g.select('.nv-y.nv-axis')
8303 //============================================================
8304 // Event Handling/Dispatching (in chart's scope)
8305 //------------------------------------------------------------
8307 legend.dispatch.on('stateChange', function(newState) {
8308 for (var key in newState)
8309 state[key] = newState[key];
8310 dispatch.stateChange(state);
8314 controls.dispatch.on('legendClick', function(d,i) {
8315 if (!d.disabled) return;
8316 controlsData = controlsData.map(function(s) {
8324 case controlLabels.grouped:
8325 multibar.stacked(false);
8328 case controlLabels.stacked:
8329 multibar.stacked(true);
8333 state.stacked = multibar.stacked();
8334 dispatch.stateChange(state);
8338 // Update chart from a state object passed to event handler
8339 dispatch.on('changeState', function(e) {
8340 if (typeof e.disabled !== 'undefined') {
8341 data.forEach(function(series,i) {
8342 series.disabled = e.disabled[i];
8344 state.disabled = e.disabled;
8346 if (typeof e.stacked !== 'undefined') {
8347 multibar.stacked(e.stacked);
8348 state.stacked = e.stacked;
8349 stacked = e.stacked;
8355 renderWatch.renderEnd('multibarchart immediate');
8359 //============================================================
8360 // Event Handling/Dispatching (out of chart's scope)
8361 //------------------------------------------------------------
8363 multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
8364 evt.value = chart.x()(evt.data);
8367 value: chart.y()(evt.data),
8370 tooltip.data(evt).hidden(false);
8373 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
8374 tooltip.hidden(true);
8377 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
8378 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
8381 //============================================================
8382 // Expose Public Variables
8383 //------------------------------------------------------------
8385 // expose chart's sub-components
8386 chart.dispatch = dispatch;
8387 chart.multibar = multibar;
8388 chart.legend = legend;
8389 chart.controls = controls;
8390 chart.xAxis = xAxis;
8391 chart.yAxis = yAxis;
8392 chart.state = state;
8393 chart.tooltip = tooltip;
8395 chart.options = nv.utils.optionsFunc.bind(chart);
8397 chart._options = Object.create({}, {
8398 // simple options, just get/set the necessary values
8399 width: {get: function(){return width;}, set: function(_){width=_;}},
8400 height: {get: function(){return height;}, set: function(_){height=_;}},
8401 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
8402 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
8403 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
8404 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
8405 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
8406 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
8407 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
8408 reduceXTicks: {get: function(){return reduceXTicks;}, set: function(_){reduceXTicks=_;}},
8409 rotateLabels: {get: function(){return rotateLabels;}, set: function(_){rotateLabels=_;}},
8410 staggerLabels: {get: function(){return staggerLabels;}, set: function(_){staggerLabels=_;}},
8412 // deprecated options
8413 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
8414 // deprecated after 1.7.1
8415 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
8416 tooltip.enabled(!!_);
8418 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
8419 // deprecated after 1.7.1
8420 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
8421 tooltip.contentGenerator(_);
8424 // options that require extra logic in the setter
8425 margin: {get: function(){return margin;}, set: function(_){
8426 margin.top = _.top !== undefined ? _.top : margin.top;
8427 margin.right = _.right !== undefined ? _.right : margin.right;
8428 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8429 margin.left = _.left !== undefined ? _.left : margin.left;
8431 duration: {get: function(){return duration;}, set: function(_){
8433 multibar.duration(duration);
8434 xAxis.duration(duration);
8435 yAxis.duration(duration);
8436 renderWatch.reset(duration);
8438 color: {get: function(){return color;}, set: function(_){
8439 color = nv.utils.getColor(_);
8440 legend.color(color);
8442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
8443 rightAlignYAxis = _;
8444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
8446 barColor: {get: function(){return multibar.barColor;}, set: function(_){
8447 multibar.barColor(_);
8448 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
8452 nv.utils.inheritOptions(chart, multibar);
8453 nv.utils.initOptions(chart);
8458 nv.models.multiBarHorizontal = function() {
8461 //============================================================
8462 // Public Variables with Default Settings
8463 //------------------------------------------------------------
8465 var margin = {top: 0, right: 0, bottom: 0, left: 0}
8468 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8470 , x = d3.scale.ordinal()
8471 , y = d3.scale.linear()
8472 , getX = function(d) { return d.x }
8473 , getY = function(d) { return d.y }
8474 , getYerr = function(d) { return d.yErr }
8475 , forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
8476 , color = nv.utils.defaultColor()
8477 , barColor = null // adding the ability to set the color for each rather than the whole group
8478 , disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
8480 , showValues = false
8481 , showBarLabels = false
8483 , groupSpacing = 0.1
8484 , valueFormat = d3.format(',.2f')
8491 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
8494 //============================================================
8495 // Private Variables
8496 //------------------------------------------------------------
8498 var x0, y0; //used to store previous scales
8499 var renderWatch = nv.utils.renderWatch(dispatch, duration);
8501 function chart(selection) {
8502 renderWatch.reset();
8503 selection.each(function(data) {
8504 var availableWidth = width - margin.left - margin.right,
8505 availableHeight = height - margin.top - margin.bottom;
8507 container = d3.select(this);
8508 nv.utils.initSVG(container);
8511 data = d3.layout.stack()
8513 .values(function(d){ return d.values })
8517 //add series index and key to each data point for reference
8518 data.forEach(function(series, i) {
8519 series.values.forEach(function(point) {
8521 point.key = series.key;
8525 // HACK for negative value stacking
8527 data[0].values.map(function(d,i) {
8528 var posBase = 0, negBase = 0;
8529 data.map(function(d) {
8531 f.size = Math.abs(f.y);
8533 f.y1 = negBase - f.size;
8534 negBase = negBase - f.size;
8538 posBase = posBase + f.size;
8544 // remap and flatten the data for use in calculating the scales' domains
8545 var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8546 data.map(function(d) {
8547 return d.values.map(function(d,i) {
8548 return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8552 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8553 .rangeBands(xRange || [0, availableHeight], groupSpacing);
8555 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)))
8557 if (showValues && !stacked)
8558 y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
8560 y.range(yRange || [0, availableWidth]);
8563 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8565 // Setup containers and skeleton of chart
8566 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
8567 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
8568 var defsEnter = wrapEnter.append('defs');
8569 var gEnter = wrapEnter.append('g');
8570 var g = wrap.select('g');
8572 gEnter.append('g').attr('class', 'nv-groups');
8573 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8575 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8576 .data(function(d) { return d }, function(d,i) { return i });
8577 groups.enter().append('g')
8578 .style('stroke-opacity', 1e-6)
8579 .style('fill-opacity', 1e-6);
8580 groups.exit().watchTransition(renderWatch, 'multibarhorizontal: exit groups')
8581 .style('stroke-opacity', 1e-6)
8582 .style('fill-opacity', 1e-6)
8585 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8586 .classed('hover', function(d) { return d.hover })
8587 .style('fill', function(d,i){ return color(d, i) })
8588 .style('stroke', function(d,i){ return color(d, i) });
8589 groups.watchTransition(renderWatch, 'multibarhorizontal: groups')
8590 .style('stroke-opacity', 1)
8591 .style('fill-opacity', .75);
8593 var bars = groups.selectAll('g.nv-bar')
8594 .data(function(d) { return d.values });
8595 bars.exit().remove();
8597 var barsEnter = bars.enter().append('g')
8598 .attr('transform', function(d,i,j) {
8599 return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
8602 barsEnter.append('rect')
8604 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8607 .on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8608 d3.select(this).classed('hover', true);
8609 dispatch.elementMouseover({
8612 color: d3.select(this).style("fill")
8615 .on('mouseout', function(d,i) {
8616 d3.select(this).classed('hover', false);
8617 dispatch.elementMouseout({
8620 color: d3.select(this).style("fill")
8623 .on('mouseout', function(d,i) {
8624 dispatch.elementMouseout({
8627 color: d3.select(this).style("fill")
8630 .on('mousemove', function(d,i) {
8631 dispatch.elementMousemove({
8634 color: d3.select(this).style("fill")
8637 .on('click', function(d,i) {
8638 dispatch.elementClick({
8641 color: d3.select(this).style("fill")
8643 d3.event.stopPropagation();
8645 .on('dblclick', function(d,i) {
8646 dispatch.elementDblClick({
8649 color: d3.select(this).style("fill")
8651 d3.event.stopPropagation();
8654 if (getYerr(data[0],0)) {
8655 barsEnter.append('polyline');
8657 bars.select('polyline')
8658 .attr('fill', 'none')
8659 .attr('points', function(d,i) {
8660 var xerr = getYerr(d,i)
8661 , mid = 0.8 * x.rangeBand() / ((stacked ? 1 : data.length) * 2);
8662 xerr = xerr.length ? xerr : [-Math.abs(xerr), Math.abs(xerr)];
8663 xerr = xerr.map(function(e) { return y(e) - y(0); });
8664 var a = [[xerr[0],-mid], [xerr[0],mid], [xerr[0],0], [xerr[1],0], [xerr[1],-mid], [xerr[1],mid]];
8665 return a.map(function (path) { return path.join(',') }).join(' ');
8667 .attr('transform', function(d,i) {
8668 var mid = x.rangeBand() / ((stacked ? 1 : data.length) * 2);
8669 return 'translate(' + (getY(d,i) < 0 ? 0 : y(getY(d,i)) - y(0)) + ', ' + mid + ')'
8673 barsEnter.append('text');
8675 if (showValues && !stacked) {
8677 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
8678 .attr('y', x.rangeBand() / (data.length * 2))
8679 .attr('dy', '.32em')
8680 .text(function(d,i) {
8681 var t = valueFormat(getY(d,i))
8682 , yerr = getYerr(d,i);
8683 if (yerr === undefined)
8686 return t + '±' + valueFormat(Math.abs(yerr));
8687 return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
8689 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8691 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8693 bars.selectAll('text').text('');
8696 if (showBarLabels && !stacked) {
8697 barsEnter.append('text').classed('nv-bar-label',true);
8698 bars.select('text.nv-bar-label')
8699 .attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
8700 .attr('y', x.rangeBand() / (data.length * 2))
8701 .attr('dy', '.32em')
8702 .text(function(d,i) { return getX(d,i) });
8703 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8704 .select('text.nv-bar-label')
8705 .attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
8708 bars.selectAll('text.nv-bar-label').text('');
8712 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8715 if (!disabled) disabled = data.map(function() { return true });
8717 .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(); })
8718 .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(); });
8722 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8723 .attr('transform', function(d,i) {
8724 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8727 .attr('width', function(d,i) {
8728 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8730 .attr('height', x.rangeBand() );
8732 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8733 .attr('transform', function(d,i) {
8734 //TODO: stacked must be all positive or all negative, not both?
8735 return 'translate(' +
8736 (getY(d,i) < 0 ? y(getY(d,i)) : y(0))
8738 (d.series * x.rangeBand() / data.length
8744 .attr('height', x.rangeBand() / data.length )
8745 .attr('width', function(d,i) {
8746 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
8749 //store old scales for use in transitions on update
8755 renderWatch.renderEnd('multibarHorizontal immediate');
8759 //============================================================
8760 // Expose Public Variables
8761 //------------------------------------------------------------
8763 chart.dispatch = dispatch;
8765 chart.options = nv.utils.optionsFunc.bind(chart);
8767 chart._options = Object.create({}, {
8768 // simple options, just get/set the necessary values
8769 width: {get: function(){return width;}, set: function(_){width=_;}},
8770 height: {get: function(){return height;}, set: function(_){height=_;}},
8771 x: {get: function(){return getX;}, set: function(_){getX=_;}},
8772 y: {get: function(){return getY;}, set: function(_){getY=_;}},
8773 yErr: {get: function(){return getYerr;}, set: function(_){getYerr=_;}},
8774 xScale: {get: function(){return x;}, set: function(_){x=_;}},
8775 yScale: {get: function(){return y;}, set: function(_){y=_;}},
8776 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
8777 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
8778 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
8779 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
8780 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
8781 stacked: {get: function(){return stacked;}, set: function(_){stacked=_;}},
8782 showValues: {get: function(){return showValues;}, set: function(_){showValues=_;}},
8783 // this shows the group name, seems pointless?
8784 //showBarLabels: {get: function(){return showBarLabels;}, set: function(_){showBarLabels=_;}},
8785 disabled: {get: function(){return disabled;}, set: function(_){disabled=_;}},
8786 id: {get: function(){return id;}, set: function(_){id=_;}},
8787 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
8788 valuePadding: {get: function(){return valuePadding;}, set: function(_){valuePadding=_;}},
8789 groupSpacing:{get: function(){return groupSpacing;}, set: function(_){groupSpacing=_;}},
8791 // options that require extra logic in the setter
8792 margin: {get: function(){return margin;}, set: function(_){
8793 margin.top = _.top !== undefined ? _.top : margin.top;
8794 margin.right = _.right !== undefined ? _.right : margin.right;
8795 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
8796 margin.left = _.left !== undefined ? _.left : margin.left;
8798 duration: {get: function(){return duration;}, set: function(_){
8800 renderWatch.reset(duration);
8802 color: {get: function(){return color;}, set: function(_){
8803 color = nv.utils.getColor(_);
8805 barColor: {get: function(){return barColor;}, set: function(_){
8806 barColor = _ ? nv.utils.getColor(_) : null;
8810 nv.utils.initOptions(chart);
8815 nv.models.multiBarHorizontalChart = function() {
8818 //============================================================
8819 // Public Variables with Default Settings
8820 //------------------------------------------------------------
8822 var multibar = nv.models.multiBarHorizontal()
8823 , xAxis = nv.models.axis()
8824 , yAxis = nv.models.axis()
8825 , legend = nv.models.legend().height(30)
8826 , controls = nv.models.legend().height(30)
8827 , tooltip = nv.models.tooltip()
8830 var margin = {top: 30, right: 20, bottom: 50, left: 60}
8833 , color = nv.utils.defaultColor()
8834 , showControls = true
8835 , controlLabels = {}
8840 , x //can be accessed via chart.xScale()
8841 , y //can be accessed via chart.yScale()
8842 , state = nv.utils.state()
8843 , defaultState = null
8845 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
8846 , controlWidth = function() { return showControls ? 180 : 0 }
8850 state.stacked = false; // DEPRECATED Maintained for backward compatibility
8852 multibar.stacked(stacked);
8858 .tickFormat(function(d) { return d })
8862 .tickFormat(d3.format(',.1f'))
8867 .valueFormatter(function(d, i) {
8868 return yAxis.tickFormat()(d, i);
8870 .headerFormatter(function(d, i) {
8871 return xAxis.tickFormat()(d, i);
8874 controls.updateState(false);
8876 //============================================================
8877 // Private Variables
8878 //------------------------------------------------------------
8880 var stateGetter = function(data) {
8883 active: data.map(function(d) { return !d.disabled }),
8889 var stateSetter = function(data) {
8890 return function(state) {
8891 if (state.stacked !== undefined)
8892 stacked = state.stacked;
8893 if (state.active !== undefined)
8894 data.forEach(function(series,i) {
8895 series.disabled = !state.active[i];
8900 var renderWatch = nv.utils.renderWatch(dispatch, duration);
8902 function chart(selection) {
8903 renderWatch.reset();
8904 renderWatch.models(multibar);
8905 if (showXAxis) renderWatch.models(xAxis);
8906 if (showYAxis) renderWatch.models(yAxis);
8908 selection.each(function(data) {
8909 var container = d3.select(this),
8911 nv.utils.initSVG(container);
8912 var availableWidth = nv.utils.availableWidth(width, container, margin),
8913 availableHeight = nv.utils.availableHeight(height, container, margin);
8915 chart.update = function() { container.transition().duration(duration).call(chart) };
8916 chart.container = this;
8918 stacked = multibar.stacked();
8921 .setter(stateSetter(data), chart.update)
8922 .getter(stateGetter(data))
8925 // DEPRECATED set state.disableddisabled
8926 state.disabled = data.map(function(d) { return !!d.disabled });
8928 if (!defaultState) {
8931 for (key in state) {
8932 if (state[key] instanceof Array)
8933 defaultState[key] = state[key].slice(0);
8935 defaultState[key] = state[key];
8939 // Display No Data message if there's nothing to show.
8940 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8941 nv.utils.noData(chart, container)
8944 container.selectAll('.nv-noData').remove();
8948 x = multibar.xScale();
8949 y = multibar.yScale();
8951 // Setup containers and skeleton of chart
8952 var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
8953 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
8954 var g = wrap.select('g');
8956 gEnter.append('g').attr('class', 'nv-x nv-axis');
8957 gEnter.append('g').attr('class', 'nv-y nv-axis')
8958 .append('g').attr('class', 'nv-zeroLine')
8960 gEnter.append('g').attr('class', 'nv-barsWrap');
8961 gEnter.append('g').attr('class', 'nv-legendWrap');
8962 gEnter.append('g').attr('class', 'nv-controlsWrap');
8966 legend.width(availableWidth - controlWidth());
8968 g.select('.nv-legendWrap')
8972 if ( margin.top != legend.height()) {
8973 margin.top = legend.height();
8974 availableHeight = nv.utils.availableHeight(height, container, margin);
8977 g.select('.nv-legendWrap')
8978 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8983 var controlsData = [
8984 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8985 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
8988 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8989 g.select('.nv-controlsWrap')
8990 .datum(controlsData)
8991 .attr('transform', 'translate(0,' + (-margin.top) +')')
8995 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8997 // Main Chart Component(s)
8999 .disabled(data.map(function(series) { return series.disabled }))
9000 .width(availableWidth)
9001 .height(availableHeight)
9002 .color(data.map(function(d,i) {
9003 return d.color || color(d, i);
9004 }).filter(function(d,i) { return !data[i].disabled }));
9006 var barsWrap = g.select('.nv-barsWrap')
9007 .datum(data.filter(function(d) { return !d.disabled }));
9009 barsWrap.transition().call(multibar);
9015 ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
9016 .tickSize(-availableWidth, 0);
9018 g.select('.nv-x.nv-axis').call(xAxis);
9020 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9023 .selectAll('line, text');
9029 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9030 .tickSize( -availableHeight, 0);
9032 g.select('.nv-y.nv-axis')
9033 .attr('transform', 'translate(0,' + availableHeight + ')');
9034 g.select('.nv-y.nv-axis').call(yAxis);
9038 g.select(".nv-zeroLine line")
9042 .attr("y2", -availableHeight)
9045 //============================================================
9046 // Event Handling/Dispatching (in chart's scope)
9047 //------------------------------------------------------------
9049 legend.dispatch.on('stateChange', function(newState) {
9050 for (var key in newState)
9051 state[key] = newState[key];
9052 dispatch.stateChange(state);
9056 controls.dispatch.on('legendClick', function(d,i) {
9057 if (!d.disabled) return;
9058 controlsData = controlsData.map(function(s) {
9066 multibar.stacked(false);
9069 multibar.stacked(true);
9073 state.stacked = multibar.stacked();
9074 dispatch.stateChange(state);
9075 stacked = multibar.stacked();
9080 // Update chart from a state object passed to event handler
9081 dispatch.on('changeState', function(e) {
9083 if (typeof e.disabled !== 'undefined') {
9084 data.forEach(function(series,i) {
9085 series.disabled = e.disabled[i];
9088 state.disabled = e.disabled;
9091 if (typeof e.stacked !== 'undefined') {
9092 multibar.stacked(e.stacked);
9093 state.stacked = e.stacked;
9094 stacked = e.stacked;
9100 renderWatch.renderEnd('multibar horizontal chart immediate');
9104 //============================================================
9105 // Event Handling/Dispatching (out of chart's scope)
9106 //------------------------------------------------------------
9108 multibar.dispatch.on('elementMouseover.tooltip', function(evt) {
9109 evt.value = chart.x()(evt.data);
9112 value: chart.y()(evt.data),
9115 tooltip.data(evt).hidden(false);
9118 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
9119 tooltip.hidden(true);
9122 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
9123 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9126 //============================================================
9127 // Expose Public Variables
9128 //------------------------------------------------------------
9130 // expose chart's sub-components
9131 chart.dispatch = dispatch;
9132 chart.multibar = multibar;
9133 chart.legend = legend;
9134 chart.controls = controls;
9135 chart.xAxis = xAxis;
9136 chart.yAxis = yAxis;
9137 chart.state = state;
9138 chart.tooltip = tooltip;
9140 chart.options = nv.utils.optionsFunc.bind(chart);
9142 chart._options = Object.create({}, {
9143 // simple options, just get/set the necessary values
9144 width: {get: function(){return width;}, set: function(_){width=_;}},
9145 height: {get: function(){return height;}, set: function(_){height=_;}},
9146 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
9147 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
9148 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
9149 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
9150 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
9151 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
9152 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
9154 // deprecated options
9155 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
9156 // deprecated after 1.7.1
9157 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
9158 tooltip.enabled(!!_);
9160 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
9161 // deprecated after 1.7.1
9162 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
9163 tooltip.contentGenerator(_);
9166 // options that require extra logic in the setter
9167 margin: {get: function(){return margin;}, set: function(_){
9168 margin.top = _.top !== undefined ? _.top : margin.top;
9169 margin.right = _.right !== undefined ? _.right : margin.right;
9170 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
9171 margin.left = _.left !== undefined ? _.left : margin.left;
9173 duration: {get: function(){return duration;}, set: function(_){
9175 renderWatch.reset(duration);
9176 multibar.duration(duration);
9177 xAxis.duration(duration);
9178 yAxis.duration(duration);
9180 color: {get: function(){return color;}, set: function(_){
9181 color = nv.utils.getColor(_);
9182 legend.color(color);
9184 barColor: {get: function(){return multibar.barColor;}, set: function(_){
9185 multibar.barColor(_);
9186 legend.color(function(d,i) {return d3.rgb('#ccc').darker(i * 1.5).toString();})
9190 nv.utils.inheritOptions(chart, multibar);
9191 nv.utils.initOptions(chart);
9195 nv.models.multiChart = function() {
9198 //============================================================
9199 // Public Variables with Default Settings
9200 //------------------------------------------------------------
9202 var margin = {top: 30, right: 20, bottom: 50, left: 60},
9203 color = nv.utils.defaultColor(),
9210 getX = function(d) { return d.x },
9211 getY = function(d) { return d.y},
9212 interpolate = 'monotone',
9216 //============================================================
9217 // Private Variables
9218 //------------------------------------------------------------
9220 var x = d3.scale.linear(),
9221 yScale1 = d3.scale.linear(),
9222 yScale2 = d3.scale.linear(),
9224 lines1 = nv.models.line().yScale(yScale1),
9225 lines2 = nv.models.line().yScale(yScale2),
9227 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9228 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9230 stack1 = nv.models.stackedArea().yScale(yScale1),
9231 stack2 = nv.models.stackedArea().yScale(yScale2),
9233 xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9234 yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9235 yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
9237 legend = nv.models.legend().height(30),
9238 tooltip = nv.models.tooltip(),
9239 dispatch = d3.dispatch();
9241 function chart(selection) {
9242 selection.each(function(data) {
9243 var container = d3.select(this),
9245 nv.utils.initSVG(container);
9247 chart.update = function() { container.transition().call(chart); };
9248 chart.container = this;
9250 var availableWidth = nv.utils.availableWidth(width, container, margin),
9251 availableHeight = nv.utils.availableHeight(height, container, margin);
9253 var dataLines1 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 1});
9254 var dataLines2 = data.filter(function(d) {return d.type == 'line' && d.yAxis == 2});
9255 var dataBars1 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 1});
9256 var dataBars2 = data.filter(function(d) {return d.type == 'bar' && d.yAxis == 2});
9257 var dataStack1 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 1});
9258 var dataStack2 = data.filter(function(d) {return d.type == 'area' && d.yAxis == 2});
9260 // Display noData message if there's nothing to show.
9261 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9262 nv.utils.noData(chart, container);
9265 container.selectAll('.nv-noData').remove();
9268 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9270 return d.values.map(function(d,i) {
9271 return { x: d.x, y: d.y }
9275 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9277 return d.values.map(function(d,i) {
9278 return { x: d.x, y: d.y }
9282 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9283 .range([0, availableWidth]);
9285 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9286 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9288 gEnter.append('g').attr('class', 'nv-x nv-axis');
9289 gEnter.append('g').attr('class', 'nv-y1 nv-axis');
9290 gEnter.append('g').attr('class', 'nv-y2 nv-axis');
9291 gEnter.append('g').attr('class', 'lines1Wrap');
9292 gEnter.append('g').attr('class', 'lines2Wrap');
9293 gEnter.append('g').attr('class', 'bars1Wrap');
9294 gEnter.append('g').attr('class', 'bars2Wrap');
9295 gEnter.append('g').attr('class', 'stack1Wrap');
9296 gEnter.append('g').attr('class', 'stack2Wrap');
9297 gEnter.append('g').attr('class', 'legendWrap');
9299 var g = wrap.select('g');
9301 var color_array = data.map(function(d,i) {
9302 return data[i].color || color(d, i);
9306 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
9307 var legendXPosition = legend.align() ? legendWidth : 0;
9309 legend.width(legendWidth);
9310 legend.color(color_array);
9312 g.select('.legendWrap')
9313 .datum(data.map(function(series) {
9314 series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
9315 series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
9320 if ( margin.top != legend.height()) {
9321 margin.top = legend.height();
9322 availableHeight = nv.utils.availableHeight(height, container, margin);
9325 g.select('.legendWrap')
9326 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
9330 .width(availableWidth)
9331 .height(availableHeight)
9332 .interpolate(interpolate)
9333 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
9335 .width(availableWidth)
9336 .height(availableHeight)
9337 .interpolate(interpolate)
9338 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
9340 .width(availableWidth)
9341 .height(availableHeight)
9342 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
9344 .width(availableWidth)
9345 .height(availableHeight)
9346 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
9348 .width(availableWidth)
9349 .height(availableHeight)
9350 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9352 .width(availableWidth)
9353 .height(availableHeight)
9354 .color(color_array.filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9356 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9358 var lines1Wrap = g.select('.lines1Wrap')
9359 .datum(dataLines1.filter(function(d){return !d.disabled}));
9360 var bars1Wrap = g.select('.bars1Wrap')
9361 .datum(dataBars1.filter(function(d){return !d.disabled}));
9362 var stack1Wrap = g.select('.stack1Wrap')
9363 .datum(dataStack1.filter(function(d){return !d.disabled}));
9364 var lines2Wrap = g.select('.lines2Wrap')
9365 .datum(dataLines2.filter(function(d){return !d.disabled}));
9366 var bars2Wrap = g.select('.bars2Wrap')
9367 .datum(dataBars2.filter(function(d){return !d.disabled}));
9368 var stack2Wrap = g.select('.stack2Wrap')
9369 .datum(dataStack2.filter(function(d){return !d.disabled}));
9371 var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
9372 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9373 }).concat([{x:0, y:0}]) : [];
9374 var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
9375 return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9376 }).concat([{x:0, y:0}]) : [];
9378 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9379 .range([0, availableHeight]);
9381 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9382 .range([0, availableHeight]);
9384 lines1.yDomain(yScale1.domain());
9385 bars1.yDomain(yScale1.domain());
9386 stack1.yDomain(yScale1.domain());
9388 lines2.yDomain(yScale2.domain());
9389 bars2.yDomain(yScale2.domain());
9390 stack2.yDomain(yScale2.domain());
9392 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9393 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9395 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9396 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9398 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9399 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9402 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9403 .tickSize(-availableHeight, 0);
9405 g.select('.nv-x.nv-axis')
9406 .attr('transform', 'translate(0,' + availableHeight + ')');
9407 d3.transition(g.select('.nv-x.nv-axis'))
9411 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9412 .tickSize( -availableWidth, 0);
9415 d3.transition(g.select('.nv-y1.nv-axis'))
9419 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9420 .tickSize( -availableWidth, 0);
9422 d3.transition(g.select('.nv-y2.nv-axis'))
9425 g.select('.nv-y1.nv-axis')
9426 .classed('nv-disabled', series1.length ? false : true)
9427 .attr('transform', 'translate(' + x.range()[0] + ',0)');
9429 g.select('.nv-y2.nv-axis')
9430 .classed('nv-disabled', series2.length ? false : true)
9431 .attr('transform', 'translate(' + x.range()[1] + ',0)');
9433 legend.dispatch.on('stateChange', function(newState) {
9437 //============================================================
9438 // Event Handling/Dispatching
9439 //------------------------------------------------------------
9441 function mouseover_line(evt) {
9442 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
9443 evt.value = evt.point.x;
9446 color: evt.point.color
9450 .valueFormatter(function(d, i) {
9451 return yaxis.tickFormat()(d, i);
9458 function mouseover_stack(evt) {
9459 var yaxis = data[evt.seriesIndex].yAxis === 2 ? yAxis2 : yAxis1;
9460 evt.point['x'] = stack1.x()(evt.point);
9461 evt.point['y'] = stack1.y()(evt.point);
9464 .valueFormatter(function(d, i) {
9465 return yaxis.tickFormat()(d, i);
9472 function mouseover_bar(evt) {
9473 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
9475 evt.value = bars1.x()(evt.data);
9477 value: bars1.y()(evt.data),
9482 .valueFormatter(function(d, i) {
9483 return yaxis.tickFormat()(d, i);
9489 lines1.dispatch.on('elementMouseover.tooltip', mouseover_line);
9490 lines2.dispatch.on('elementMouseover.tooltip', mouseover_line);
9491 lines1.dispatch.on('elementMouseout.tooltip', function(evt) {
9492 tooltip.hidden(true)
9494 lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
9495 tooltip.hidden(true)
9498 stack1.dispatch.on('elementMouseover.tooltip', mouseover_stack);
9499 stack2.dispatch.on('elementMouseover.tooltip', mouseover_stack);
9500 stack1.dispatch.on('elementMouseout.tooltip', function(evt) {
9501 tooltip.hidden(true)
9503 stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
9504 tooltip.hidden(true)
9507 bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
9508 bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
9510 bars1.dispatch.on('elementMouseout.tooltip', function(evt) {
9511 tooltip.hidden(true);
9513 bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
9514 tooltip.hidden(true);
9516 bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
9517 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9519 bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
9520 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9528 //============================================================
9529 // Global getters and setters
9530 //------------------------------------------------------------
9532 chart.dispatch = dispatch;
9533 chart.lines1 = lines1;
9534 chart.lines2 = lines2;
9535 chart.bars1 = bars1;
9536 chart.bars2 = bars2;
9537 chart.stack1 = stack1;
9538 chart.stack2 = stack2;
9539 chart.xAxis = xAxis;
9540 chart.yAxis1 = yAxis1;
9541 chart.yAxis2 = yAxis2;
9542 chart.tooltip = tooltip;
9544 chart.options = nv.utils.optionsFunc.bind(chart);
9546 chart._options = Object.create({}, {
9547 // simple options, just get/set the necessary values
9548 width: {get: function(){return width;}, set: function(_){width=_;}},
9549 height: {get: function(){return height;}, set: function(_){height=_;}},
9550 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
9551 yDomain1: {get: function(){return yDomain1;}, set: function(_){yDomain1=_;}},
9552 yDomain2: {get: function(){return yDomain2;}, set: function(_){yDomain2=_;}},
9553 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
9554 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
9556 // deprecated options
9557 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
9558 // deprecated after 1.7.1
9559 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
9560 tooltip.enabled(!!_);
9562 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
9563 // deprecated after 1.7.1
9564 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
9565 tooltip.contentGenerator(_);
9568 // options that require extra logic in the setter
9569 margin: {get: function(){return margin;}, set: function(_){
9570 margin.top = _.top !== undefined ? _.top : margin.top;
9571 margin.right = _.right !== undefined ? _.right : margin.right;
9572 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
9573 margin.left = _.left !== undefined ? _.left : margin.left;
9575 color: {get: function(){return color;}, set: function(_){
9576 color = nv.utils.getColor(_);
9578 x: {get: function(){return getX;}, set: function(_){
9587 y: {get: function(){return getY;}, set: function(_){
9596 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
9598 lines1.useVoronoi(_);
9599 lines2.useVoronoi(_);
9600 stack1.useVoronoi(_);
9601 stack2.useVoronoi(_);
9605 nv.utils.initOptions(chart);
9611 nv.models.ohlcBar = function() {
9614 //============================================================
9615 // Public Variables with Default Settings
9616 //------------------------------------------------------------
9618 var margin = {top: 0, right: 0, bottom: 0, left: 0}
9621 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9623 , x = d3.scale.linear()
9624 , y = d3.scale.linear()
9625 , getX = function(d) { return d.x }
9626 , getY = function(d) { return d.y }
9627 , getOpen = function(d) { return d.open }
9628 , getClose = function(d) { return d.close }
9629 , getHigh = function(d) { return d.high }
9630 , getLow = function(d) { return d.low }
9633 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9635 , color = nv.utils.defaultColor()
9636 , interactive = false
9641 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd', 'chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove')
9644 //============================================================
9645 // Private Variables
9646 //------------------------------------------------------------
9648 function chart(selection) {
9649 selection.each(function(data) {
9650 container = d3.select(this);
9651 var availableWidth = nv.utils.availableWidth(width, container, margin),
9652 availableHeight = nv.utils.availableHeight(height, container, margin);
9654 nv.utils.initSVG(container);
9657 var w = (availableWidth / data[0].values.length) * .9;
9660 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9663 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
9665 x.range(xRange || [5 + w/2, availableWidth - w/2 - 5]);
9667 y.domain(yDomain || [
9668 d3.min(data[0].values.map(getLow).concat(forceY)),
9669 d3.max(data[0].values.map(getHigh).concat(forceY))
9671 ).range(yRange || [availableHeight, 0]);
9673 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9674 if (x.domain()[0] === x.domain()[1])
9676 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9679 if (y.domain()[0] === y.domain()[1])
9681 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9684 // Setup containers and skeleton of chart
9685 var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
9686 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
9687 var defsEnter = wrapEnter.append('defs');
9688 var gEnter = wrapEnter.append('g');
9689 var g = wrap.select('g');
9691 gEnter.append('g').attr('class', 'nv-ticks');
9693 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9696 .on('click', function(d,i) {
9697 dispatch.chartClick({
9705 defsEnter.append('clipPath')
9706 .attr('id', 'nv-chart-clip-path-' + id)
9709 wrap.select('#nv-chart-clip-path-' + id + ' rect')
9710 .attr('width', availableWidth)
9711 .attr('height', availableHeight);
9713 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9715 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9716 .data(function(d) { return d });
9717 ticks.exit().remove();
9719 ticks.enter().append('path')
9720 .attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9721 .attr('d', function(d,i) {
9730 + (y(getLow(d,i)) - y(getOpen(d,i)))
9740 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9741 .attr('fill', function(d,i) { return color[0]; })
9742 .attr('stroke', function(d,i) { return color[0]; })
9744 .attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
9745 .attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
9747 // the bar colors are controlled by CSS currently
9748 ticks.attr('class', function(d,i,j) {
9749 return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i;
9752 d3.transition(ticks)
9753 .attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9754 .attr('d', function(d,i) {
9755 var w = (availableWidth / data[0].values.length) * .9;
9781 //Create methods to allow outside functions to highlight a specific bar.
9782 chart.highlightPoint = function(pointIndex, isHoverOver) {
9783 chart.clearHighlights();
9784 container.select(".nv-ohlcBar .nv-tick-0-" + pointIndex)
9785 .classed("hover", isHoverOver)
9789 chart.clearHighlights = function() {
9790 container.select(".nv-ohlcBar .nv-tick.hover")
9791 .classed("hover", false)
9795 //============================================================
9796 // Expose Public Variables
9797 //------------------------------------------------------------
9799 chart.dispatch = dispatch;
9800 chart.options = nv.utils.optionsFunc.bind(chart);
9802 chart._options = Object.create({}, {
9803 // simple options, just get/set the necessary values
9804 width: {get: function(){return width;}, set: function(_){width=_;}},
9805 height: {get: function(){return height;}, set: function(_){height=_;}},
9806 xScale: {get: function(){return x;}, set: function(_){x=_;}},
9807 yScale: {get: function(){return y;}, set: function(_){y=_;}},
9808 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
9809 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
9810 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
9811 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
9812 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
9813 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
9814 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
9815 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
9816 id: {get: function(){return id;}, set: function(_){id=_;}},
9817 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
9819 x: {get: function(){return getX;}, set: function(_){getX=_;}},
9820 y: {get: function(){return getY;}, set: function(_){getY=_;}},
9821 open: {get: function(){return getOpen();}, set: function(_){getOpen=_;}},
9822 close: {get: function(){return getClose();}, set: function(_){getClose=_;}},
9823 high: {get: function(){return getHigh;}, set: function(_){getHigh=_;}},
9824 low: {get: function(){return getLow;}, set: function(_){getLow=_;}},
9826 // options that require extra logic in the setter
9827 margin: {get: function(){return margin;}, set: function(_){
9828 margin.top = _.top != undefined ? _.top : margin.top;
9829 margin.right = _.right != undefined ? _.right : margin.right;
9830 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
9831 margin.left = _.left != undefined ? _.left : margin.left;
9833 color: {get: function(){return color;}, set: function(_){
9834 color = nv.utils.getColor(_);
9838 nv.utils.initOptions(chart);
9841 // Code adapted from Jason Davies' "Parallel Coordinates"
9842 // http://bl.ocks.org/jasondavies/1341281
9843 nv.models.parallelCoordinates = function() {
9846 //============================================================
9847 // Public Variables with Default Settings
9848 //------------------------------------------------------------
9850 var margin = {top: 30, right: 0, bottom: 10, left: 0}
9853 , x = d3.scale.ordinal()
9855 , dimensionNames = []
9856 , dimensionFormats = []
9857 , color = nv.utils.defaultColor()
9862 , dispatch = d3.dispatch('brush', 'elementMouseover', 'elementMouseout')
9865 //============================================================
9866 // Private Variables
9867 //------------------------------------------------------------
9869 function chart(selection) {
9870 selection.each(function(data) {
9871 var container = d3.select(this);
9872 var availableWidth = nv.utils.availableWidth(width, container, margin),
9873 availableHeight = nv.utils.availableHeight(height, container, margin);
9875 nv.utils.initSVG(container);
9877 active = data; //set all active before first brush call
9880 x.rangePoints([0, availableWidth], 1).domain(dimensionNames);
9882 //Set as true if all values on an axis are missing.
9883 var onlyNanValues = {};
9884 // Extract the list of dimensions and create a scale for each.
9885 dimensionNames.forEach(function(d) {
9886 var extent = d3.extent(data, function(p) { return +p[d]; });
9887 onlyNanValues[d] = false;
9888 //If there is no values to display on an axis, set the extent to 0
9889 if (extent[0] === undefined) {
9890 onlyNanValues[d] = true;
9894 //Scale axis if there is only one value
9895 if (extent[0] === extent[1]) {
9896 extent[0] = extent[0] - 1;
9897 extent[1] = extent[1] + 1;
9899 //Use 90% of (availableHeight - 12) for the axis range, 12 reprensenting the space necessary to display "undefined values" text.
9900 //The remaining 10% are used to display the missingValue line.
9901 y[d] = d3.scale.linear()
9903 .range([(availableHeight - 12) * 0.9, 0]);
9905 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
9910 // Setup containers and skeleton of chart
9911 var wrap = container.selectAll('g.nv-wrap.nv-parallelCoordinates').data([data]);
9912 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-parallelCoordinates');
9913 var gEnter = wrapEnter.append('g');
9914 var g = wrap.select('g');
9916 gEnter.append('g').attr('class', 'nv-parallelCoordinates background');
9917 gEnter.append('g').attr('class', 'nv-parallelCoordinates foreground');
9918 gEnter.append('g').attr('class', 'nv-parallelCoordinates missingValuesline');
9920 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9922 var line = d3.svg.line().interpolate('cardinal').tension(lineTension),
9923 axis = d3.svg.axis().orient('left'),
9924 axisDrag = d3.behavior.drag()
9925 .on('dragstart', dragStart)
9926 .on('drag', dragMove)
9927 .on('dragend', dragEnd);
9929 //Add missing value line at the bottom of the chart
9930 var missingValuesline, missingValueslineText;
9931 var step = x.range()[1] - x.range()[0];
9932 var axisWithMissingValues = [];
9933 var lineData = [0 + step / 2, availableHeight - 12, availableWidth - step / 2, availableHeight - 12];
9934 missingValuesline = wrap.select('.missingValuesline').selectAll('line').data([lineData]);
9935 missingValuesline.enter().append('line');
9936 missingValuesline.exit().remove();
9937 missingValuesline.attr("x1", function(d) { return d[0]; })
9938 .attr("y1", function(d) { return d[1]; })
9939 .attr("x2", function(d) { return d[2]; })
9940 .attr("y2", function(d) { return d[3]; });
9942 //Add the text "undefined values" under the missing value line
9943 missingValueslineText = wrap.select('.missingValuesline').selectAll('text').data(["undefined values"]);
9944 missingValueslineText.append('text').data(["undefined values"]);
9945 missingValueslineText.enter().append('text');
9946 missingValueslineText.exit().remove();
9947 missingValueslineText.attr("y", availableHeight)
9948 //To have the text right align with the missingValues line, substract 92 representing the text size.
9949 .attr("x", availableWidth - 92 - step / 2)
9950 .text(function(d) { return d; });
9952 // Add grey background lines for context.
9953 var background = wrap.select('.background').selectAll('path').data(data);
9954 background.enter().append('path');
9955 background.exit().remove();
9956 background.attr('d', path);
9958 // Add blue foreground lines for focus.
9959 var foreground = wrap.select('.foreground').selectAll('path').data(data);
9960 foreground.enter().append('path')
9961 foreground.exit().remove();
9962 foreground.attr('d', path).attr('stroke', color);
9963 foreground.on("mouseover", function (d, i) {
9964 d3.select(this).classed('hover', true);
9965 dispatch.elementMouseover({
9969 pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]]
9973 foreground.on("mouseout", function (d, i) {
9974 d3.select(this).classed('hover', false);
9975 dispatch.elementMouseout({
9982 // Add a group element for each dimension.
9983 var dimensions = g.selectAll('.dimension').data(dimensionNames);
9984 var dimensionsEnter = dimensions.enter().append('g').attr('class', 'nv-parallelCoordinates dimension');
9985 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates nv-axis');
9986 dimensionsEnter.append('g').attr('class', 'nv-parallelCoordinates-brush');
9987 dimensionsEnter.append('text').attr('class', 'nv-parallelCoordinates nv-label');
9989 dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
9990 dimensions.exit().remove();
9992 // Add an axis and title.
9993 dimensions.select('.nv-label')
9994 .style("cursor", "move")
9996 .attr('text-anchor', 'middle')
9998 .on("mouseover", function(d, i) {
9999 dispatch.elementMouseover({
10001 pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]]
10004 .on("mouseout", function(d, i) {
10005 dispatch.elementMouseout({
10011 dimensions.select('.nv-axis')
10012 .each(function (d, i) {
10013 d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i])));
10016 dimensions.select('.nv-parallelCoordinates-brush')
10017 .each(function (d) {
10018 d3.select(this).call(y[d].brush);
10022 .attr('width', 16);
10024 // Returns the path for a given data point.
10026 return line(dimensionNames.map(function (p) {
10027 //If value if missing, put the value on the missing value line
10028 if(isNaN(d[p]) || isNaN(parseFloat(d[p]))) {
10029 var domain = y[p].domain();
10030 var range = y[p].range();
10031 var min = domain[0] - (domain[1] - domain[0]) / 9;
10033 //If it's not already the case, allow brush to select undefined values
10034 if(axisWithMissingValues.indexOf(p) < 0) {
10036 var newscale = d3.scale.linear().domain([min, domain[1]]).range([availableHeight - 12, range[1]]);
10037 y[p].brush.y(newscale);
10038 axisWithMissingValues.push(p);
10041 return [x(p), y[p](min)];
10044 //If parallelCoordinate contain missing values show the missing values line otherwise, hide it.
10045 if(axisWithMissingValues.length > 0) {
10046 missingValuesline.style("display", "inline");
10047 missingValueslineText.style("display", "inline");
10049 missingValuesline.style("display", "none");
10050 missingValueslineText.style("display", "none");
10053 return [x(p), y[p](d[p])];
10057 // Handles a brush event, toggling the display of foreground lines.
10059 var actives = dimensionNames.filter(function(p) { return !y[p].brush.empty(); }),
10060 extents = actives.map(function(p) { return y[p].brush.extent(); });
10062 filters = []; //erase current filters
10063 actives.forEach(function(d,i) {
10070 active = []; //erase current active list
10071 foreground.style('display', function(d) {
10072 var isActive = actives.every(function(p, i) {
10073 if(isNaN(d[p]) && extents[i][0] == y[p].brush.y().domain()[0]) return true;
10074 return extents[i][0] <= d[p] && d[p] <= extents[i][1];
10076 if (isActive) active.push(d);
10077 return isActive ? null : 'none';
10086 function dragStart(d, i) {
10087 dragging[d] = this.parentNode.__origin__ = x(d);
10088 background.attr("visibility", "hidden");
10092 function dragMove(d, i) {
10093 dragging[d] = Math.min(availableWidth, Math.max(0, this.parentNode.__origin__ += d3.event.x));
10094 foreground.attr("d", path);
10095 dimensionNames.sort(function (a, b) { return position(a) - position(b); });
10096 x.domain(dimensionNames);
10097 dimensions.attr("transform", function(d) { return "translate(" + position(d) + ")"; });
10100 function dragEnd(d, i) {
10101 delete this.parentNode.__origin__;
10102 delete dragging[d];
10103 d3.select(this.parentNode).attr("transform", "translate(" + x(d) + ")");
10108 .attr("visibility", null);
10112 function position(d) {
10113 var v = dragging[d];
10114 return v == null ? x(d) : v;
10121 //============================================================
10122 // Expose Public Variables
10123 //------------------------------------------------------------
10125 chart.dispatch = dispatch;
10126 chart.options = nv.utils.optionsFunc.bind(chart);
10128 chart._options = Object.create({}, {
10129 // simple options, just get/set the necessary values
10130 width: {get: function(){return width;}, set: function(_){width= _;}},
10131 height: {get: function(){return height;}, set: function(_){height= _;}},
10132 dimensionNames: {get: function() { return dimensionNames;}, set: function(_){dimensionNames= _;}},
10133 dimensionFormats : {get: function(){return dimensionFormats;}, set: function (_){dimensionFormats=_;}},
10134 lineTension: {get: function(){return lineTension;}, set: function(_){lineTension = _;}},
10136 // deprecated options
10137 dimensions: {get: function (){return dimensionNames;}, set: function(_){
10138 // deprecated after 1.8.1
10139 nv.deprecated('dimensions', 'use dimensionNames instead');
10140 dimensionNames = _;
10143 // options that require extra logic in the setter
10144 margin: {get: function(){return margin;}, set: function(_){
10145 margin.top = _.top !== undefined ? _.top : margin.top;
10146 margin.right = _.right !== undefined ? _.right : margin.right;
10147 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10148 margin.left = _.left !== undefined ? _.left : margin.left;
10150 color: {get: function(){return color;}, set: function(_){
10151 color = nv.utils.getColor(_);
10155 nv.utils.initOptions(chart);
10158 nv.models.pie = function() {
10161 //============================================================
10162 // Public Variables with Default Settings
10163 //------------------------------------------------------------
10165 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10168 , getX = function(d) { return d.x }
10169 , getY = function(d) { return d.y }
10170 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10172 , color = nv.utils.defaultColor()
10173 , valueFormat = d3.format(',.2f')
10174 , showLabels = true
10175 , labelsOutside = false
10176 , labelType = "key"
10177 , labelThreshold = .02 //if slice percentage is under this, don't show label
10180 , growOnHover = true
10182 , labelSunbeamLayout = false
10183 , startAngle = false
10189 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
10195 //============================================================
10197 //------------------------------------------------------------
10199 var renderWatch = nv.utils.renderWatch(dispatch);
10201 function chart(selection) {
10202 renderWatch.reset();
10203 selection.each(function(data) {
10204 var availableWidth = width - margin.left - margin.right
10205 , availableHeight = height - margin.top - margin.bottom
10206 , radius = Math.min(availableWidth, availableHeight) / 2
10207 , arcsRadiusOuter = []
10208 , arcsRadiusInner = []
10211 container = d3.select(this)
10212 if (arcsRadius.length === 0) {
10213 var outer = radius - radius / 5;
10214 var inner = donutRatio * radius;
10215 for (var i = 0; i < data[0].length; i++) {
10216 arcsRadiusOuter.push(outer);
10217 arcsRadiusInner.push(inner);
10220 arcsRadiusOuter = arcsRadius.map(function (d) { return (d.outer - d.outer / 5) * radius; });
10221 arcsRadiusInner = arcsRadius.map(function (d) { return (d.inner - d.inner / 5) * radius; });
10222 donutRatio = d3.min(arcsRadius.map(function (d) { return (d.inner - d.inner / 5); }));
10224 nv.utils.initSVG(container);
10226 // Setup containers and skeleton of chart
10227 var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10228 var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10229 var gEnter = wrapEnter.append('g');
10230 var g = wrap.select('g');
10231 var g_pie = gEnter.append('g').attr('class', 'nv-pie');
10232 gEnter.append('g').attr('class', 'nv-pieLabels');
10234 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10235 g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10236 g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10239 container.on('click', function(d,i) {
10240 dispatch.chartClick({
10250 for (var i = 0; i < data[0].length; i++) {
10252 var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
10253 var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
10255 if (startAngle !== false) {
10256 arc.startAngle(startAngle);
10257 arcOver.startAngle(startAngle);
10259 if (endAngle !== false) {
10260 arc.endAngle(endAngle);
10261 arcOver.endAngle(endAngle);
10264 arc.innerRadius(arcsRadiusInner[i]);
10265 arcOver.innerRadius(arcsRadiusInner[i]);
10268 if (arc.cornerRadius && cornerRadius) {
10269 arc.cornerRadius(cornerRadius);
10270 arcOver.cornerRadius(cornerRadius);
10274 arcsOver.push(arcOver);
10277 // Setup the Pie chart and choose the data element
10278 var pie = d3.layout.pie()
10280 .value(function(d) { return d.disabled ? 0 : getY(d) });
10282 // padAngle added in d3 3.5
10283 if (pie.padAngle && padAngle) {
10284 pie.padAngle(padAngle);
10287 // if title is specified and donut, put it in the middle
10288 if (donut && title) {
10289 g_pie.append("text").attr('class', 'nv-pie-title');
10291 wrap.select('.nv-pie-title')
10292 .style("text-anchor", "middle")
10293 .text(function (d) {
10296 .style("font-size", (Math.min(availableWidth, availableHeight)) * donutRatio * 2 / (title.length + 2) + "px")
10297 .attr("dy", "0.35em") // trick to vertically center text
10298 .attr('transform', function(d, i) {
10299 return 'translate(0, '+ titleOffset + ')';
10303 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
10304 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
10306 slices.exit().remove();
10307 pieLabels.exit().remove();
10309 var ae = slices.enter().append('g');
10310 ae.attr('class', 'nv-slice');
10311 ae.on('mouseover', function(d, i) {
10312 d3.select(this).classed('hover', true);
10314 d3.select(this).select("path").transition()
10316 .attr("d", arcsOver[i]);
10318 dispatch.elementMouseover({
10321 color: d3.select(this).style("fill")
10324 ae.on('mouseout', function(d, i) {
10325 d3.select(this).classed('hover', false);
10327 d3.select(this).select("path").transition()
10329 .attr("d", arcs[i]);
10331 dispatch.elementMouseout({data: d.data, index: i});
10333 ae.on('mousemove', function(d, i) {
10334 dispatch.elementMousemove({data: d.data, index: i});
10336 ae.on('click', function(d, i) {
10337 dispatch.elementClick({
10340 color: d3.select(this).style("fill")
10343 ae.on('dblclick', function(d, i) {
10344 dispatch.elementDblClick({
10347 color: d3.select(this).style("fill")
10351 slices.attr('fill', function(d,i) { return color(d.data, i); });
10352 slices.attr('stroke', function(d,i) { return color(d.data, i); });
10354 var paths = ae.append('path').each(function(d) {
10358 slices.select('path')
10360 .attr('d', function (d, i) { return arcs[i](d); })
10361 .attrTween('d', arcTween);
10364 // This does the normal label
10365 var labelsArc = [];
10366 for (var i = 0; i < data[0].length; i++) {
10367 labelsArc.push(arcs[i]);
10369 if (labelsOutside) {
10371 labelsArc[i] = d3.svg.arc().outerRadius(arcs[i].outerRadius());
10372 if (startAngle !== false) labelsArc[i].startAngle(startAngle);
10373 if (endAngle !== false) labelsArc[i].endAngle(endAngle);
10375 } else if (!donut) {
10376 labelsArc[i].innerRadius(0);
10380 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
10381 var group = d3.select(this);
10383 group.attr('transform', function (d, i) {
10384 if (labelSunbeamLayout) {
10385 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
10386 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
10387 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10388 if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
10393 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
10395 d.outerRadius = radius + 10; // Set Outer Coordinate
10396 d.innerRadius = radius + 15; // Set Inner Coordinate
10397 return 'translate(' + labelsArc[i].centroid(d) + ')'
10401 group.append('rect')
10402 .style('stroke', '#fff')
10403 .style('fill', '#fff')
10407 group.append('text')
10408 .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
10409 .style('fill', '#000')
10412 var labelLocationHash = {};
10413 var avgHeight = 14;
10414 var avgWidth = 140;
10415 var createHashKey = function(coordinates) {
10416 return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
10419 pieLabels.watchTransition(renderWatch, 'pie labels').attr('transform', function (d, i) {
10420 if (labelSunbeamLayout) {
10421 d.outerRadius = arcsRadiusOuter[i] + 10; // Set Outer Coordinate
10422 d.innerRadius = arcsRadiusOuter[i] + 15; // Set Inner Coordinate
10423 var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10424 if ((d.startAngle + d.endAngle) / 2 < Math.PI) {
10429 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
10431 d.outerRadius = radius + 10; // Set Outer Coordinate
10432 d.innerRadius = radius + 15; // Set Inner Coordinate
10435 Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
10436 Each label location is hashed, and if a hash collision occurs, we assume an overlap.
10437 Adjust the label's y-position to remove the overlap.
10439 var center = labelsArc[i].centroid(d);
10441 var hashKey = createHashKey(center);
10442 if (labelLocationHash[hashKey]) {
10443 center[1] -= avgHeight;
10445 labelLocationHash[createHashKey(center)] = true;
10447 return 'translate(' + center + ')'
10451 pieLabels.select(".nv-label text")
10452 .style('text-anchor', function(d,i) {
10453 //center the text on it's origin or begin/end if orthogonal aligned
10454 return labelSunbeamLayout ? ((d.startAngle + d.endAngle) / 2 < Math.PI ? 'start' : 'end') : 'middle';
10456 .text(function(d, i) {
10457 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10459 if (!d.value || percent < labelThreshold) return '';
10461 if(typeof labelType === 'function') {
10462 label = labelType(d, i, {
10463 'key': getX(d.data),
10464 'value': getY(d.data),
10465 'percent': valueFormat(percent)
10468 switch (labelType) {
10470 label = getX(d.data);
10473 label = valueFormat(getY(d.data));
10476 label = d3.format('%')(percent);
10486 // Computes the angle of an arc, converting from radians to degrees.
10487 function angle(d) {
10488 var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
10489 return a > 90 ? a - 180 : a;
10492 function arcTween(a, idx) {
10493 a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
10494 a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
10495 if (!donut) a.innerRadius = 0;
10496 var i = d3.interpolate(this._current, a);
10497 this._current = i(0);
10498 return function (t) {
10499 return arcs[idx](i(t));
10504 renderWatch.renderEnd('pie immediate');
10508 //============================================================
10509 // Expose Public Variables
10510 //------------------------------------------------------------
10512 chart.dispatch = dispatch;
10513 chart.options = nv.utils.optionsFunc.bind(chart);
10515 chart._options = Object.create({}, {
10516 // simple options, just get/set the necessary values
10517 arcsRadius: { get: function () { return arcsRadius; }, set: function (_) { arcsRadius = _; } },
10518 width: {get: function(){return width;}, set: function(_){width=_;}},
10519 height: {get: function(){return height;}, set: function(_){height=_;}},
10520 showLabels: {get: function(){return showLabels;}, set: function(_){showLabels=_;}},
10521 title: {get: function(){return title;}, set: function(_){title=_;}},
10522 titleOffset: {get: function(){return titleOffset;}, set: function(_){titleOffset=_;}},
10523 labelThreshold: {get: function(){return labelThreshold;}, set: function(_){labelThreshold=_;}},
10524 valueFormat: {get: function(){return valueFormat;}, set: function(_){valueFormat=_;}},
10525 x: {get: function(){return getX;}, set: function(_){getX=_;}},
10526 id: {get: function(){return id;}, set: function(_){id=_;}},
10527 endAngle: {get: function(){return endAngle;}, set: function(_){endAngle=_;}},
10528 startAngle: {get: function(){return startAngle;}, set: function(_){startAngle=_;}},
10529 padAngle: {get: function(){return padAngle;}, set: function(_){padAngle=_;}},
10530 cornerRadius: {get: function(){return cornerRadius;}, set: function(_){cornerRadius=_;}},
10531 donutRatio: {get: function(){return donutRatio;}, set: function(_){donutRatio=_;}},
10532 labelsOutside: {get: function(){return labelsOutside;}, set: function(_){labelsOutside=_;}},
10533 labelSunbeamLayout: {get: function(){return labelSunbeamLayout;}, set: function(_){labelSunbeamLayout=_;}},
10534 donut: {get: function(){return donut;}, set: function(_){donut=_;}},
10535 growOnHover: {get: function(){return growOnHover;}, set: function(_){growOnHover=_;}},
10537 // depreciated after 1.7.1
10538 pieLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
10540 nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
10542 // depreciated after 1.7.1
10543 donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
10545 nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
10547 // deprecated after 1.7.1
10548 labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
10550 nv.deprecated('labelFormat','use valueFormat instead');
10553 // options that require extra logic in the setter
10554 margin: {get: function(){return margin;}, set: function(_){
10555 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10556 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10557 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10558 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10560 y: {get: function(){return getY;}, set: function(_){
10561 getY=d3.functor(_);
10563 color: {get: function(){return color;}, set: function(_){
10564 color=nv.utils.getColor(_);
10566 labelType: {get: function(){return labelType;}, set: function(_){
10567 labelType= _ || 'key';
10571 nv.utils.initOptions(chart);
10574 nv.models.pieChart = function() {
10577 //============================================================
10578 // Public Variables with Default Settings
10579 //------------------------------------------------------------
10581 var pie = nv.models.pie();
10582 var legend = nv.models.legend();
10583 var tooltip = nv.models.tooltip();
10585 var margin = {top: 30, right: 20, bottom: 20, left: 20}
10588 , showLegend = true
10589 , legendPosition = "top"
10590 , color = nv.utils.defaultColor()
10591 , state = nv.utils.state()
10592 , defaultState = null
10595 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
10599 .headerEnabled(false)
10601 .valueFormatter(function(d, i) {
10602 return pie.valueFormat()(d, i);
10605 //============================================================
10606 // Private Variables
10607 //------------------------------------------------------------
10609 var renderWatch = nv.utils.renderWatch(dispatch);
10611 var stateGetter = function(data) {
10614 active: data.map(function(d) { return !d.disabled })
10619 var stateSetter = function(data) {
10620 return function(state) {
10621 if (state.active !== undefined) {
10622 data.forEach(function (series, i) {
10623 series.disabled = !state.active[i];
10629 //============================================================
10631 //------------------------------------------------------------
10633 function chart(selection) {
10634 renderWatch.reset();
10635 renderWatch.models(pie);
10637 selection.each(function(data) {
10638 var container = d3.select(this);
10639 nv.utils.initSVG(container);
10642 var availableWidth = nv.utils.availableWidth(width, container, margin),
10643 availableHeight = nv.utils.availableHeight(height, container, margin);
10645 chart.update = function() { container.transition().call(chart); };
10646 chart.container = this;
10648 state.setter(stateSetter(data), chart.update)
10649 .getter(stateGetter(data))
10652 //set state.disabled
10653 state.disabled = data.map(function(d) { return !!d.disabled });
10655 if (!defaultState) {
10658 for (key in state) {
10659 if (state[key] instanceof Array)
10660 defaultState[key] = state[key].slice(0);
10662 defaultState[key] = state[key];
10666 // Display No Data message if there's nothing to show.
10667 if (!data || !data.length) {
10668 nv.utils.noData(chart, container);
10671 container.selectAll('.nv-noData').remove();
10674 // Setup containers and skeleton of chart
10675 var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
10676 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
10677 var g = wrap.select('g');
10679 gEnter.append('g').attr('class', 'nv-pieWrap');
10680 gEnter.append('g').attr('class', 'nv-legendWrap');
10684 if (legendPosition === "top") {
10685 legend.width( availableWidth ).key(pie.x());
10687 wrap.select('.nv-legendWrap')
10691 if ( margin.top != legend.height()) {
10692 margin.top = legend.height();
10693 availableHeight = nv.utils.availableHeight(height, container, margin);
10696 wrap.select('.nv-legendWrap')
10697 .attr('transform', 'translate(0,' + (-margin.top) +')');
10698 } else if (legendPosition === "right") {
10699 var legendWidth = nv.models.legend().width();
10700 if (availableWidth / 2 < legendWidth) {
10701 legendWidth = (availableWidth / 2)
10703 legend.height(availableHeight).key(pie.x());
10704 legend.width(legendWidth);
10705 availableWidth -= legend.width();
10707 wrap.select('.nv-legendWrap')
10710 .attr('transform', 'translate(' + (availableWidth) +',0)');
10713 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10715 // Main Chart Component(s)
10716 pie.width(availableWidth).height(availableHeight);
10717 var pieWrap = g.select('.nv-pieWrap').datum([data]);
10718 d3.transition(pieWrap).call(pie);
10720 //============================================================
10721 // Event Handling/Dispatching (in chart's scope)
10722 //------------------------------------------------------------
10724 legend.dispatch.on('stateChange', function(newState) {
10725 for (var key in newState) {
10726 state[key] = newState[key];
10728 dispatch.stateChange(state);
10732 // Update chart from a state object passed to event handler
10733 dispatch.on('changeState', function(e) {
10734 if (typeof e.disabled !== 'undefined') {
10735 data.forEach(function(series,i) {
10736 series.disabled = e.disabled[i];
10738 state.disabled = e.disabled;
10744 renderWatch.renderEnd('pieChart immediate');
10748 //============================================================
10749 // Event Handling/Dispatching (out of chart's scope)
10750 //------------------------------------------------------------
10752 pie.dispatch.on('elementMouseover.tooltip', function(evt) {
10754 key: chart.x()(evt.data),
10755 value: chart.y()(evt.data),
10758 tooltip.data(evt).hidden(false);
10761 pie.dispatch.on('elementMouseout.tooltip', function(evt) {
10762 tooltip.hidden(true);
10765 pie.dispatch.on('elementMousemove.tooltip', function(evt) {
10766 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
10769 //============================================================
10770 // Expose Public Variables
10771 //------------------------------------------------------------
10773 // expose chart's sub-components
10774 chart.legend = legend;
10775 chart.dispatch = dispatch;
10777 chart.tooltip = tooltip;
10778 chart.options = nv.utils.optionsFunc.bind(chart);
10780 // use Object get/set functionality to map between vars and chart functions
10781 chart._options = Object.create({}, {
10782 // simple options, just get/set the necessary values
10783 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
10784 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
10785 legendPosition: {get: function(){return legendPosition;}, set: function(_){legendPosition=_;}},
10786 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
10788 // deprecated options
10789 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
10790 // deprecated after 1.7.1
10791 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
10792 tooltip.enabled(!!_);
10794 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
10795 // deprecated after 1.7.1
10796 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
10797 tooltip.contentGenerator(_);
10800 // options that require extra logic in the setter
10801 color: {get: function(){return color;}, set: function(_){
10803 legend.color(color);
10806 duration: {get: function(){return duration;}, set: function(_){
10808 renderWatch.reset(duration);
10810 margin: {get: function(){return margin;}, set: function(_){
10811 margin.top = _.top !== undefined ? _.top : margin.top;
10812 margin.right = _.right !== undefined ? _.right : margin.right;
10813 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
10814 margin.left = _.left !== undefined ? _.left : margin.left;
10817 nv.utils.inheritOptions(chart, pie);
10818 nv.utils.initOptions(chart);
10822 nv.models.scatter = function() {
10825 //============================================================
10826 // Public Variables with Default Settings
10827 //------------------------------------------------------------
10829 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10832 , color = nv.utils.defaultColor() // chooses color
10833 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10835 , x = d3.scale.linear()
10836 , y = d3.scale.linear()
10837 , z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
10838 , getX = function(d) { return d.x } // accessor to get the x value
10839 , getY = function(d) { return d.y } // accessor to get the y value
10840 , getSize = function(d) { return d.size || 1} // accessor to get the point size
10841 , getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
10842 , forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
10843 , forceY = [] // List of numbers to Force into the Y scale
10844 , forceSize = [] // List of numbers to Force into the Size scale
10845 , interactive = true // If true, plots a voronoi overlay for advanced point intersection
10846 , pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
10847 , padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10848 , padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
10849 , clipEdge = false // if true, masks points within x and y scale
10850 , clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
10851 , showVoronoi = false // display the voronoi areas
10852 , clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
10853 , xDomain = null // Override x domain (skips the calculation from data)
10854 , yDomain = null // Override y domain
10855 , xRange = null // Override x range
10856 , yRange = null // Override y range
10857 , sizeDomain = null // Override point size domain
10859 , singlePoint = false
10860 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
10861 , useVoronoi = true
10866 //============================================================
10867 // Private Variables
10868 //------------------------------------------------------------
10870 var x0, y0, z0 // used to store previous scales
10872 , needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
10873 , renderWatch = nv.utils.renderWatch(dispatch, duration)
10874 , _sizeRange_def = [16, 256]
10877 function chart(selection) {
10878 renderWatch.reset();
10879 selection.each(function(data) {
10880 container = d3.select(this);
10881 var availableWidth = nv.utils.availableWidth(width, container, margin),
10882 availableHeight = nv.utils.availableHeight(height, container, margin);
10884 nv.utils.initSVG(container);
10886 //add series index to each data point for reference
10887 data.forEach(function(series, i) {
10888 series.values.forEach(function(point) {
10894 // remap and flatten the data for use in calculating the scales' domains
10895 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
10897 data.map(function(d) {
10898 return d.values.map(function(d,i) {
10899 return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
10904 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
10906 if (padData && data[0])
10907 x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
10908 //x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
10910 x.range(xRange || [0, availableWidth]);
10912 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
10913 .range(yRange || [availableHeight, 0]);
10915 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10916 .range(sizeRange || _sizeRange_def);
10918 // If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10919 singlePoint = x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1];
10921 if (x.domain()[0] === x.domain()[1])
10923 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10924 : x.domain([-1,1]);
10926 if (y.domain()[0] === y.domain()[1])
10928 y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
10929 : y.domain([-1,1]);
10931 if ( isNaN(x.domain()[0])) {
10935 if ( isNaN(y.domain()[0])) {
10943 // Setup containers and skeleton of chart
10944 var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
10945 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id);
10946 var defsEnter = wrapEnter.append('defs');
10947 var gEnter = wrapEnter.append('g');
10948 var g = wrap.select('g');
10950 wrap.classed('nv-single-point', singlePoint);
10951 gEnter.append('g').attr('class', 'nv-groups');
10952 gEnter.append('g').attr('class', 'nv-point-paths');
10953 wrapEnter.append('g').attr('class', 'nv-point-clips');
10955 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10957 defsEnter.append('clipPath')
10958 .attr('id', 'nv-edge-clip-' + id)
10961 wrap.select('#nv-edge-clip-' + id + ' rect')
10962 .attr('width', availableWidth)
10963 .attr('height', (availableHeight > 0) ? availableHeight : 0);
10965 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10967 function updateInteractiveLayer() {
10968 // Always clear needs-update flag regardless of whether or not
10969 // we will actually do anything (avoids needless invocations).
10970 needsUpdate = false;
10972 if (!interactive) return false;
10974 // inject series and point index for reference into voronoi
10975 if (useVoronoi === true) {
10976 var vertices = d3.merge(data.map(function(group, groupIndex) {
10977 return group.values
10978 .map(function(point, pointIndex) {
10979 // *Adding noise to make duplicates very unlikely
10980 // *Injecting series and point index for reference
10981 /* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
10983 var pX = getX(point,pointIndex);
10984 var pY = getY(point,pointIndex);
10986 return [x(pX)+ Math.random() * 1e-4,
10987 y(pY)+ Math.random() * 1e-4,
10989 pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
10991 .filter(function(pointArray, pointIndex) {
10992 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10997 if (vertices.length == 0) return false; // No active points, we're done
10998 if (vertices.length < 3) {
10999 // Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
11000 vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
11001 vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11002 vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11003 vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11006 // keep voronoi sections from going more than 10 outside of graph
11007 // to avoid overlap with other things like legend etc
11008 var bounds = d3.geom.polygon([
11011 [width + 10,height + 10],
11015 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11017 'data': bounds.clip(d),
11018 'series': vertices[i][2],
11019 'point': vertices[i][3]
11023 // nuke all voronoi paths on reload and recreate them
11024 wrap.select('.nv-point-paths').selectAll('path').remove();
11025 var pointPaths = wrap.select('.nv-point-paths').selectAll('path').data(voronoi);
11026 var vPointPaths = pointPaths
11027 .enter().append("svg:path")
11028 .attr("d", function(d) {
11029 if (!d || !d.data || d.data.length === 0)
11032 return "M" + d.data.join(",") + "Z";
11034 .attr("id", function(d,i) {
11035 return "nv-path-"+i; })
11036 .attr("clip-path", function(d,i) { return "url(#nv-clip-"+i+")"; })
11039 // good for debugging point hover issues
11041 vPointPaths.style("fill", d3.rgb(230, 230, 230))
11042 .style('fill-opacity', 0.4)
11043 .style('stroke-opacity', 1)
11044 .style("stroke", d3.rgb(200,200,200));
11048 // voronoi sections are already set to clip,
11049 // just create the circles with the IDs they expect
11050 wrap.select('.nv-point-clips').selectAll('clipPath').remove();
11051 wrap.select('.nv-point-clips').selectAll("clipPath")
11053 .enter().append("svg:clipPath")
11054 .attr("id", function(d, i) { return "nv-clip-"+i;})
11055 .append("svg:circle")
11056 .attr('cx', function(d) { return d[0]; })
11057 .attr('cy', function(d) { return d[1]; })
11058 .attr('r', clipRadius);
11061 var mouseEventCallback = function(d, mDispatch) {
11062 if (needsUpdate) return 0;
11063 var series = data[d.series];
11064 if (series === undefined) return;
11065 var point = series.values[d.point];
11066 point['color'] = color(series, d.series);
11068 // standardize attributes for tooltip.
11069 point['x'] = getX(point);
11070 point['y'] = getY(point);
11072 // can't just get box of event node since it's actually a voronoi polygon
11073 var box = container.node().getBoundingClientRect();
11074 var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
11075 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
11078 left: x(getX(point, d.point)) + box.left + scrollLeft + margin.left + 10,
11079 top: y(getY(point, d.point)) + box.top + scrollTop + margin.top + 10
11086 seriesIndex: d.series,
11087 pointIndex: d.point
11092 .on('click', function(d) {
11093 mouseEventCallback(d, dispatch.elementClick);
11095 .on('dblclick', function(d) {
11096 mouseEventCallback(d, dispatch.elementDblClick);
11098 .on('mouseover', function(d) {
11099 mouseEventCallback(d, dispatch.elementMouseover);
11101 .on('mouseout', function(d, i) {
11102 mouseEventCallback(d, dispatch.elementMouseout);
11106 // add event handlers to points instead voronoi paths
11107 wrap.select('.nv-groups').selectAll('.nv-group')
11108 .selectAll('.nv-point')
11109 //.data(dataWithPoints)
11110 //.style('pointer-events', 'auto') // recativate events, disabled by css
11111 .on('click', function(d,i) {
11112 //nv.log('test', d, i);
11113 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11114 var series = data[d.series],
11115 point = series.values[i];
11117 dispatch.elementClick({
11120 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11121 seriesIndex: d.series,
11125 .on('dblclick', function(d,i) {
11126 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11127 var series = data[d.series],
11128 point = series.values[i];
11130 dispatch.elementDblClick({
11133 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11134 seriesIndex: d.series,
11138 .on('mouseover', function(d,i) {
11139 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11140 var series = data[d.series],
11141 point = series.values[i];
11143 dispatch.elementMouseover({
11146 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11147 seriesIndex: d.series,
11152 .on('mouseout', function(d,i) {
11153 if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11154 var series = data[d.series],
11155 point = series.values[i];
11157 dispatch.elementMouseout({
11160 seriesIndex: d.series,
11168 needsUpdate = true;
11169 var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11170 .data(function(d) { return d }, function(d) { return d.key });
11171 groups.enter().append('g')
11172 .style('stroke-opacity', 1e-6)
11173 .style('fill-opacity', 1e-6);
11177 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11178 .classed('hover', function(d) { return d.hover });
11179 groups.watchTransition(renderWatch, 'scatter: groups')
11180 .style('fill', function(d,i) { return color(d, i) })
11181 .style('stroke', function(d,i) { return color(d, i) })
11182 .style('stroke-opacity', 1)
11183 .style('fill-opacity', .5);
11185 // create the points, maintaining their IDs from the original data set
11186 var points = groups.selectAll('path.nv-point')
11187 .data(function(d) {
11188 return d.values.map(
11189 function (point, pointIndex) {
11190 return [point, pointIndex]
11192 function(pointArray, pointIndex) {
11193 return pointActive(pointArray[0], pointIndex)
11196 points.enter().append('path')
11197 .style('fill', function (d) { return d.color })
11198 .style('stroke', function (d) { return d.color })
11199 .attr('transform', function(d) {
11200 return 'translate(' + x0(getX(d[0],d[1])) + ',' + y0(getY(d[0],d[1])) + ')'
11204 .type(function(d) { return getShape(d[0]); })
11205 .size(function(d) { return z(getSize(d[0],d[1])) })
11207 points.exit().remove();
11208 groups.exit().selectAll('path.nv-point')
11209 .watchTransition(renderWatch, 'scatter exit')
11210 .attr('transform', function(d) {
11211 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
11214 points.each(function(d) {
11216 .classed('nv-point', true)
11217 .classed('nv-point-' + d[1], true)
11218 .classed('nv-noninteractive', !interactive)
11219 .classed('hover',false)
11223 .watchTransition(renderWatch, 'scatter points')
11224 .attr('transform', function(d) {
11225 //nv.log(d, getX(d[0],d[1]), x(getX(d[0],d[1])));
11226 return 'translate(' + x(getX(d[0],d[1])) + ',' + y(getY(d[0],d[1])) + ')'
11230 .type(function(d) { return getShape(d[0]); })
11231 .size(function(d) { return z(getSize(d[0],d[1])) })
11234 // Delay updating the invisible interactive layer for smoother animation
11235 clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11236 timeoutID = setTimeout(updateInteractiveLayer, 300);
11237 //updateInteractiveLayer();
11239 //store old scales for use in transitions on update
11245 renderWatch.renderEnd('scatter immediate');
11249 //============================================================
11250 // Expose Public Variables
11251 //------------------------------------------------------------
11253 chart.dispatch = dispatch;
11254 chart.options = nv.utils.optionsFunc.bind(chart);
11256 // utility function calls provided by this chart
11257 chart._calls = new function() {
11258 this.clearHighlights = function () {
11259 nv.dom.write(function() {
11260 container.selectAll(".nv-point.hover").classed("hover", false);
11264 this.highlightPoint = function (seriesIndex, pointIndex, isHoverOver) {
11265 nv.dom.write(function() {
11266 container.select(" .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11267 .classed("hover", isHoverOver);
11272 // trigger calls from events too
11273 dispatch.on('elementMouseover.point', function(d) {
11274 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
11277 dispatch.on('elementMouseout.point', function(d) {
11278 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
11281 chart._options = Object.create({}, {
11282 // simple options, just get/set the necessary values
11283 width: {get: function(){return width;}, set: function(_){width=_;}},
11284 height: {get: function(){return height;}, set: function(_){height=_;}},
11285 xScale: {get: function(){return x;}, set: function(_){x=_;}},
11286 yScale: {get: function(){return y;}, set: function(_){y=_;}},
11287 pointScale: {get: function(){return z;}, set: function(_){z=_;}},
11288 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
11289 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
11290 pointDomain: {get: function(){return sizeDomain;}, set: function(_){sizeDomain=_;}},
11291 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
11292 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
11293 pointRange: {get: function(){return sizeRange;}, set: function(_){sizeRange=_;}},
11294 forceX: {get: function(){return forceX;}, set: function(_){forceX=_;}},
11295 forceY: {get: function(){return forceY;}, set: function(_){forceY=_;}},
11296 forcePoint: {get: function(){return forceSize;}, set: function(_){forceSize=_;}},
11297 interactive: {get: function(){return interactive;}, set: function(_){interactive=_;}},
11298 pointActive: {get: function(){return pointActive;}, set: function(_){pointActive=_;}},
11299 padDataOuter: {get: function(){return padDataOuter;}, set: function(_){padDataOuter=_;}},
11300 padData: {get: function(){return padData;}, set: function(_){padData=_;}},
11301 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
11302 clipVoronoi: {get: function(){return clipVoronoi;}, set: function(_){clipVoronoi=_;}},
11303 clipRadius: {get: function(){return clipRadius;}, set: function(_){clipRadius=_;}},
11304 showVoronoi: {get: function(){return showVoronoi;}, set: function(_){showVoronoi=_;}},
11305 id: {get: function(){return id;}, set: function(_){id=_;}},
11308 // simple functor options
11309 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
11310 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
11311 pointSize: {get: function(){return getSize;}, set: function(_){getSize = d3.functor(_);}},
11312 pointShape: {get: function(){return getShape;}, set: function(_){getShape = d3.functor(_);}},
11314 // options that require extra logic in the setter
11315 margin: {get: function(){return margin;}, set: function(_){
11316 margin.top = _.top !== undefined ? _.top : margin.top;
11317 margin.right = _.right !== undefined ? _.right : margin.right;
11318 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11319 margin.left = _.left !== undefined ? _.left : margin.left;
11321 duration: {get: function(){return duration;}, set: function(_){
11323 renderWatch.reset(duration);
11325 color: {get: function(){return color;}, set: function(_){
11326 color = nv.utils.getColor(_);
11328 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
11330 if (useVoronoi === false) {
11331 clipVoronoi = false;
11336 nv.utils.initOptions(chart);
11340 nv.models.scatterChart = function() {
11343 //============================================================
11344 // Public Variables with Default Settings
11345 //------------------------------------------------------------
11347 var scatter = nv.models.scatter()
11348 , xAxis = nv.models.axis()
11349 , yAxis = nv.models.axis()
11350 , legend = nv.models.legend()
11351 , distX = nv.models.distribution()
11352 , distY = nv.models.distribution()
11353 , tooltip = nv.models.tooltip()
11356 var margin = {top: 30, right: 20, bottom: 50, left: 75}
11360 , color = nv.utils.defaultColor()
11361 , x = scatter.xScale()
11362 , y = scatter.yScale()
11363 , showDistX = false
11364 , showDistY = false
11365 , showLegend = true
11368 , rightAlignYAxis = false
11369 , state = nv.utils.state()
11370 , defaultState = null
11371 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
11376 scatter.xScale(x).yScale(y);
11377 xAxis.orient('bottom').tickPadding(10);
11379 .orient((rightAlignYAxis) ? 'right' : 'left')
11385 .headerFormatter(function(d, i) {
11386 return xAxis.tickFormat()(d, i);
11388 .valueFormatter(function(d, i) {
11389 return yAxis.tickFormat()(d, i);
11392 //============================================================
11393 // Private Variables
11394 //------------------------------------------------------------
11397 , renderWatch = nv.utils.renderWatch(dispatch, duration);
11399 var stateGetter = function(data) {
11402 active: data.map(function(d) { return !d.disabled })
11407 var stateSetter = function(data) {
11408 return function(state) {
11409 if (state.active !== undefined)
11410 data.forEach(function(series,i) {
11411 series.disabled = !state.active[i];
11416 function chart(selection) {
11417 renderWatch.reset();
11418 renderWatch.models(scatter);
11419 if (showXAxis) renderWatch.models(xAxis);
11420 if (showYAxis) renderWatch.models(yAxis);
11421 if (showDistX) renderWatch.models(distX);
11422 if (showDistY) renderWatch.models(distY);
11424 selection.each(function(data) {
11427 container = d3.select(this);
11428 nv.utils.initSVG(container);
11430 var availableWidth = nv.utils.availableWidth(width, container, margin),
11431 availableHeight = nv.utils.availableHeight(height, container, margin);
11433 chart.update = function() {
11434 if (duration === 0)
11435 container.call(chart);
11437 container.transition().duration(duration).call(chart);
11439 chart.container = this;
11442 .setter(stateSetter(data), chart.update)
11443 .getter(stateGetter(data))
11446 // DEPRECATED set state.disableddisabled
11447 state.disabled = data.map(function(d) { return !!d.disabled });
11449 if (!defaultState) {
11452 for (key in state) {
11453 if (state[key] instanceof Array)
11454 defaultState[key] = state[key].slice(0);
11456 defaultState[key] = state[key];
11460 // Display noData message if there's nothing to show.
11461 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11462 nv.utils.noData(chart, container);
11463 renderWatch.renderEnd('scatter immediate');
11466 container.selectAll('.nv-noData').remove();
11470 x = scatter.xScale();
11471 y = scatter.yScale();
11473 // Setup containers and skeleton of chart
11474 var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11475 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11476 var gEnter = wrapEnter.append('g');
11477 var g = wrap.select('g');
11479 // background for pointer events
11480 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
11482 gEnter.append('g').attr('class', 'nv-x nv-axis');
11483 gEnter.append('g').attr('class', 'nv-y nv-axis');
11484 gEnter.append('g').attr('class', 'nv-scatterWrap');
11485 gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
11486 gEnter.append('g').attr('class', 'nv-distWrap');
11487 gEnter.append('g').attr('class', 'nv-legendWrap');
11489 if (rightAlignYAxis) {
11490 g.select(".nv-y.nv-axis")
11491 .attr("transform", "translate(" + availableWidth + ",0)");
11496 var legendWidth = availableWidth;
11497 legend.width(legendWidth);
11499 wrap.select('.nv-legendWrap')
11503 if ( margin.top != legend.height()) {
11504 margin.top = legend.height();
11505 availableHeight = nv.utils.availableHeight(height, container, margin);
11508 wrap.select('.nv-legendWrap')
11509 .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
11512 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11514 // Main Chart Component(s)
11516 .width(availableWidth)
11517 .height(availableHeight)
11518 .color(data.map(function(d,i) {
11519 d.color = d.color || color(d, i);
11521 }).filter(function(d,i) { return !data[i].disabled }));
11523 wrap.select('.nv-scatterWrap')
11524 .datum(data.filter(function(d) { return !d.disabled }))
11528 wrap.select('.nv-regressionLinesWrap')
11529 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
11531 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
11532 .data(function (d) {
11536 regWrap.enter().append('g').attr('class', 'nv-regLines');
11538 var regLine = regWrap.selectAll('.nv-regLine')
11539 .data(function (d) {
11544 .append('line').attr('class', 'nv-regLine')
11545 .style('stroke-opacity', 0);
11547 // don't add lines unless we have slope and intercept to use
11548 regLine.filter(function(d) {
11549 return d.intercept && d.slope;
11551 .watchTransition(renderWatch, 'scatterPlusLineChart: regline')
11552 .attr('x1', x.range()[0])
11553 .attr('x2', x.range()[1])
11554 .attr('y1', function (d, i) {
11555 return y(x.domain()[0] * d.slope + d.intercept)
11557 .attr('y2', function (d, i) {
11558 return y(x.domain()[1] * d.slope + d.intercept)
11560 .style('stroke', function (d, i, j) {
11563 .style('stroke-opacity', function (d, i) {
11564 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
11571 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
11572 .tickSize( -availableHeight , 0);
11574 g.select('.nv-x.nv-axis')
11575 .attr('transform', 'translate(0,' + y.range()[0] + ')')
11582 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
11583 .tickSize( -availableWidth, 0);
11585 g.select('.nv-y.nv-axis')
11592 .getData(scatter.x())
11594 .width(availableWidth)
11595 .color(data.map(function(d,i) {
11596 return d.color || color(d, i);
11597 }).filter(function(d,i) { return !data[i].disabled }));
11598 gEnter.select('.nv-distWrap').append('g')
11599 .attr('class', 'nv-distributionX');
11600 g.select('.nv-distributionX')
11601 .attr('transform', 'translate(0,' + y.range()[0] + ')')
11602 .datum(data.filter(function(d) { return !d.disabled }))
11608 .getData(scatter.y())
11610 .width(availableHeight)
11611 .color(data.map(function(d,i) {
11612 return d.color || color(d, i);
11613 }).filter(function(d,i) { return !data[i].disabled }));
11614 gEnter.select('.nv-distWrap').append('g')
11615 .attr('class', 'nv-distributionY');
11616 g.select('.nv-distributionY')
11617 .attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
11618 .datum(data.filter(function(d) { return !d.disabled }))
11622 //============================================================
11623 // Event Handling/Dispatching (in chart's scope)
11624 //------------------------------------------------------------
11626 legend.dispatch.on('stateChange', function(newState) {
11627 for (var key in newState)
11628 state[key] = newState[key];
11629 dispatch.stateChange(state);
11633 // Update chart from a state object passed to event handler
11634 dispatch.on('changeState', function(e) {
11635 if (typeof e.disabled !== 'undefined') {
11636 data.forEach(function(series,i) {
11637 series.disabled = e.disabled[i];
11639 state.disabled = e.disabled;
11644 // mouseover needs availableHeight so we just keep scatter mouse events inside the chart block
11645 scatter.dispatch.on('elementMouseout.tooltip', function(evt) {
11646 tooltip.hidden(true);
11647 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
11649 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
11650 .attr('x2', distY.size());
11653 scatter.dispatch.on('elementMouseover.tooltip', function(evt) {
11654 container.select('.nv-series-' + evt.seriesIndex + ' .nv-distx-' + evt.pointIndex)
11655 .attr('y1', evt.pos.top - availableHeight - margin.top);
11656 container.select('.nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
11657 .attr('x2', evt.pos.left + distX.size() - margin.left);
11658 tooltip.position(evt.pos).data(evt).hidden(false);
11661 //store old scales for use in transitions on update
11667 renderWatch.renderEnd('scatter with line immediate');
11671 //============================================================
11672 // Expose Public Variables
11673 //------------------------------------------------------------
11675 // expose chart's sub-components
11676 chart.dispatch = dispatch;
11677 chart.scatter = scatter;
11678 chart.legend = legend;
11679 chart.xAxis = xAxis;
11680 chart.yAxis = yAxis;
11681 chart.distX = distX;
11682 chart.distY = distY;
11683 chart.tooltip = tooltip;
11685 chart.options = nv.utils.optionsFunc.bind(chart);
11686 chart._options = Object.create({}, {
11687 // simple options, just get/set the necessary values
11688 width: {get: function(){return width;}, set: function(_){width=_;}},
11689 height: {get: function(){return height;}, set: function(_){height=_;}},
11690 container: {get: function(){return container;}, set: function(_){container=_;}},
11691 showDistX: {get: function(){return showDistX;}, set: function(_){showDistX=_;}},
11692 showDistY: {get: function(){return showDistY;}, set: function(_){showDistY=_;}},
11693 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
11694 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
11695 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
11696 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
11697 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
11698 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
11700 // deprecated options
11701 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
11702 // deprecated after 1.7.1
11703 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
11704 tooltip.enabled(!!_);
11706 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
11707 // deprecated after 1.7.1
11708 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
11709 tooltip.contentGenerator(_);
11711 tooltipXContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
11712 // deprecated after 1.7.1
11713 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
11715 tooltipYContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
11716 // deprecated after 1.7.1
11717 nv.deprecated('tooltipContent', 'This option is removed, put values into main tooltip.');
11720 // options that require extra logic in the setter
11721 margin: {get: function(){return margin;}, set: function(_){
11722 margin.top = _.top !== undefined ? _.top : margin.top;
11723 margin.right = _.right !== undefined ? _.right : margin.right;
11724 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11725 margin.left = _.left !== undefined ? _.left : margin.left;
11727 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
11728 rightAlignYAxis = _;
11729 yAxis.orient( (_) ? 'right' : 'left');
11731 color: {get: function(){return color;}, set: function(_){
11732 color = nv.utils.getColor(_);
11733 legend.color(color);
11734 distX.color(color);
11735 distY.color(color);
11739 nv.utils.inheritOptions(chart, scatter);
11740 nv.utils.initOptions(chart);
11744 nv.models.sparkline = function() {
11747 //============================================================
11748 // Public Variables with Default Settings
11749 //------------------------------------------------------------
11751 var margin = {top: 2, right: 0, bottom: 2, left: 0}
11756 , x = d3.scale.linear()
11757 , y = d3.scale.linear()
11758 , getX = function(d) { return d.x }
11759 , getY = function(d) { return d.y }
11760 , color = nv.utils.getColor(['#000'])
11767 function chart(selection) {
11768 selection.each(function(data) {
11769 var availableWidth = width - margin.left - margin.right,
11770 availableHeight = height - margin.top - margin.bottom;
11772 container = d3.select(this);
11773 nv.utils.initSVG(container);
11776 x .domain(xDomain || d3.extent(data, getX ))
11777 .range(xRange || [0, availableWidth]);
11779 y .domain(yDomain || d3.extent(data, getY ))
11780 .range(yRange || [availableHeight, 0]);
11782 // Setup containers and skeleton of chart
11783 var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
11784 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
11785 var gEnter = wrapEnter.append('g');
11786 var g = wrap.select('g');
11788 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
11790 var paths = wrap.selectAll('path')
11791 .data(function(d) { return [d] });
11792 paths.enter().append('path');
11793 paths.exit().remove();
11795 .style('stroke', function(d,i) { return d.color || color(d, i) })
11796 .attr('d', d3.svg.line()
11797 .x(function(d,i) { return x(getX(d,i)) })
11798 .y(function(d,i) { return y(getY(d,i)) })
11801 // TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
11802 var points = wrap.selectAll('circle.nv-point')
11803 .data(function(data) {
11804 var yValues = data.map(function(d, i) { return getY(d,i); });
11805 function pointIndex(index) {
11807 var result = data[index];
11808 result.pointIndex = index;
11814 var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
11815 minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
11816 currentPoint = pointIndex(yValues.length - 1);
11817 return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
11819 points.enter().append('circle');
11820 points.exit().remove();
11822 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
11823 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
11825 .attr('class', function(d,i) {
11826 return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
11827 getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
11834 //============================================================
11835 // Expose Public Variables
11836 //------------------------------------------------------------
11838 chart.options = nv.utils.optionsFunc.bind(chart);
11840 chart._options = Object.create({}, {
11841 // simple options, just get/set the necessary values
11842 width: {get: function(){return width;}, set: function(_){width=_;}},
11843 height: {get: function(){return height;}, set: function(_){height=_;}},
11844 xDomain: {get: function(){return xDomain;}, set: function(_){xDomain=_;}},
11845 yDomain: {get: function(){return yDomain;}, set: function(_){yDomain=_;}},
11846 xRange: {get: function(){return xRange;}, set: function(_){xRange=_;}},
11847 yRange: {get: function(){return yRange;}, set: function(_){yRange=_;}},
11848 xScale: {get: function(){return x;}, set: function(_){x=_;}},
11849 yScale: {get: function(){return y;}, set: function(_){y=_;}},
11850 animate: {get: function(){return animate;}, set: function(_){animate=_;}},
11853 x: {get: function(){return getX;}, set: function(_){getX=d3.functor(_);}},
11854 y: {get: function(){return getY;}, set: function(_){getY=d3.functor(_);}},
11856 // options that require extra logic in the setter
11857 margin: {get: function(){return margin;}, set: function(_){
11858 margin.top = _.top !== undefined ? _.top : margin.top;
11859 margin.right = _.right !== undefined ? _.right : margin.right;
11860 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
11861 margin.left = _.left !== undefined ? _.left : margin.left;
11863 color: {get: function(){return color;}, set: function(_){
11864 color = nv.utils.getColor(_);
11868 nv.utils.initOptions(chart);
11872 nv.models.sparklinePlus = function() {
11875 //============================================================
11876 // Public Variables with Default Settings
11877 //------------------------------------------------------------
11879 var sparkline = nv.models.sparkline();
11881 var margin = {top: 15, right: 100, bottom: 10, left: 50}
11888 , xTickFormat = d3.format(',r')
11889 , yTickFormat = d3.format(',.2f')
11890 , showLastValue = true
11891 , alignValue = true
11892 , rightAlignValue = false
11896 function chart(selection) {
11897 selection.each(function(data) {
11898 var container = d3.select(this);
11899 nv.utils.initSVG(container);
11901 var availableWidth = nv.utils.availableWidth(width, container, margin),
11902 availableHeight = nv.utils.availableHeight(height, container, margin);
11904 chart.update = function() { container.call(chart); };
11905 chart.container = this;
11907 // Display No Data message if there's nothing to show.
11908 if (!data || !data.length) {
11909 nv.utils.noData(chart, container)
11912 container.selectAll('.nv-noData').remove();
11915 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
11918 x = sparkline.xScale();
11919 y = sparkline.yScale();
11921 // Setup containers and skeleton of chart
11922 var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
11923 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
11924 var gEnter = wrapEnter.append('g');
11925 var g = wrap.select('g');
11927 gEnter.append('g').attr('class', 'nv-sparklineWrap');
11928 gEnter.append('g').attr('class', 'nv-valueWrap');
11929 gEnter.append('g').attr('class', 'nv-hoverArea');
11931 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11933 // Main Chart Component(s)
11934 var sparklineWrap = g.select('.nv-sparklineWrap');
11936 sparkline.width(availableWidth).height(availableHeight);
11937 sparklineWrap.call(sparkline);
11939 if (showLastValue) {
11940 var valueWrap = g.select('.nv-valueWrap');
11941 var value = valueWrap.selectAll('.nv-currentValue')
11942 .data([currentValue]);
11944 value.enter().append('text').attr('class', 'nv-currentValue')
11945 .attr('dx', rightAlignValue ? -8 : 8)
11946 .attr('dy', '.9em')
11947 .style('text-anchor', rightAlignValue ? 'end' : 'start');
11950 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
11951 .attr('y', alignValue ? function (d) {
11954 .style('fill', sparkline.color()(data[data.length - 1], data.length - 1))
11955 .text(yTickFormat(currentValue));
11958 gEnter.select('.nv-hoverArea').append('rect')
11959 .on('mousemove', sparklineHover)
11960 .on('click', function() { paused = !paused })
11961 .on('mouseout', function() { index = []; updateValueLine(); });
11963 g.select('.nv-hoverArea rect')
11964 .attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
11965 .attr('width', availableWidth + margin.left + margin.right)
11966 .attr('height', availableHeight + margin.top);
11968 //index is currently global (within the chart), may or may not keep it that way
11969 function updateValueLine() {
11970 if (paused) return;
11972 var hoverValue = g.selectAll('.nv-hoverValue').data(index);
11974 var hoverEnter = hoverValue.enter()
11975 .append('g').attr('class', 'nv-hoverValue')
11976 .style('stroke-opacity', 0)
11977 .style('fill-opacity', 0);
11980 .transition().duration(250)
11981 .style('stroke-opacity', 0)
11982 .style('fill-opacity', 0)
11986 .attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
11987 .transition().duration(250)
11988 .style('stroke-opacity', 1)
11989 .style('fill-opacity', 1);
11991 if (!index.length) return;
11993 hoverEnter.append('line')
11995 .attr('y1', -margin.top)
11997 .attr('y2', availableHeight);
11999 hoverEnter.append('text').attr('class', 'nv-xValue')
12001 .attr('y', -margin.top)
12002 .attr('text-anchor', 'end')
12003 .attr('dy', '.9em');
12005 g.select('.nv-hoverValue .nv-xValue')
12006 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
12008 hoverEnter.append('text').attr('class', 'nv-yValue')
12010 .attr('y', -margin.top)
12011 .attr('text-anchor', 'start')
12012 .attr('dy', '.9em');
12014 g.select('.nv-hoverValue .nv-yValue')
12015 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
12018 function sparklineHover() {
12019 if (paused) return;
12021 var pos = d3.mouse(this)[0] - margin.left;
12023 function getClosestIndex(data, x) {
12024 var distance = Math.abs(sparkline.x()(data[0], 0) - x);
12025 var closestIndex = 0;
12026 for (var i = 0; i < data.length; i++){
12027 if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
12028 distance = Math.abs(sparkline.x()(data[i], i) - x);
12032 return closestIndex;
12035 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
12044 //============================================================
12045 // Expose Public Variables
12046 //------------------------------------------------------------
12048 // expose chart's sub-components
12049 chart.sparkline = sparkline;
12051 chart.options = nv.utils.optionsFunc.bind(chart);
12053 chart._options = Object.create({}, {
12054 // simple options, just get/set the necessary values
12055 width: {get: function(){return width;}, set: function(_){width=_;}},
12056 height: {get: function(){return height;}, set: function(_){height=_;}},
12057 xTickFormat: {get: function(){return xTickFormat;}, set: function(_){xTickFormat=_;}},
12058 yTickFormat: {get: function(){return yTickFormat;}, set: function(_){yTickFormat=_;}},
12059 showLastValue: {get: function(){return showLastValue;}, set: function(_){showLastValue=_;}},
12060 alignValue: {get: function(){return alignValue;}, set: function(_){alignValue=_;}},
12061 rightAlignValue: {get: function(){return rightAlignValue;}, set: function(_){rightAlignValue=_;}},
12062 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
12064 // options that require extra logic in the setter
12065 margin: {get: function(){return margin;}, set: function(_){
12066 margin.top = _.top !== undefined ? _.top : margin.top;
12067 margin.right = _.right !== undefined ? _.right : margin.right;
12068 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
12069 margin.left = _.left !== undefined ? _.left : margin.left;
12073 nv.utils.inheritOptions(chart, sparkline);
12074 nv.utils.initOptions(chart);
12079 nv.models.stackedArea = function() {
12082 //============================================================
12083 // Public Variables with Default Settings
12084 //------------------------------------------------------------
12086 var margin = {top: 0, right: 0, bottom: 0, left: 0}
12089 , color = nv.utils.defaultColor() // a function that computes the color
12090 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
12092 , getX = function(d) { return d.x } // accessor to get the x value from a data point
12093 , getY = function(d) { return d.y } // accessor to get the y value from a data point
12096 , order = 'default'
12097 , interpolate = 'linear' // controls the line interpolation
12098 , clipEdge = false // if true, masks lines within x and y scale
12099 , x //can be accessed via chart.xScale()
12100 , y //can be accessed via chart.yScale()
12101 , scatter = nv.models.scatter()
12103 , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
12107 .pointSize(2.2) // default size
12108 .pointDomain([2.2, 2.2]) // all the same size by default
12111 /************************************
12113 * 'wiggle' (stream)
12115 * 'expand' (normalize to 100%)
12116 * 'silhouette' (simple centered)
12119 * 'inside-out' (stream)
12120 * 'default' (input order)
12121 ************************************/
12123 var renderWatch = nv.utils.renderWatch(dispatch, duration);
12125 function chart(selection) {
12126 renderWatch.reset();
12127 renderWatch.models(scatter);
12128 selection.each(function(data) {
12129 var availableWidth = width - margin.left - margin.right,
12130 availableHeight = height - margin.top - margin.bottom;
12132 container = d3.select(this);
12133 nv.utils.initSVG(container);
12136 x = scatter.xScale();
12137 y = scatter.yScale();
12139 var dataRaw = data;
12140 // Injecting point index into each point because d3.layout.stack().out does not give index
12141 data.forEach(function(aseries, i) {
12142 aseries.seriesIndex = i;
12143 aseries.values = aseries.values.map(function(d, j) {
12150 var dataFiltered = data.filter(function(series) {
12151 return !series.disabled;
12154 data = d3.layout.stack()
12157 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
12160 .out(function(d, y0, y) {
12168 // Setup containers and skeleton of chart
12169 var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
12170 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
12171 var defsEnter = wrapEnter.append('defs');
12172 var gEnter = wrapEnter.append('g');
12173 var g = wrap.select('g');
12175 gEnter.append('g').attr('class', 'nv-areaWrap');
12176 gEnter.append('g').attr('class', 'nv-scatterWrap');
12178 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12180 // If the user has not specified forceY, make sure 0 is included in the domain
12181 // Otherwise, use user-specified values for forceY
12182 if (scatter.forceY().length == 0) {
12183 scatter.forceY().push(0);
12187 .width(availableWidth)
12188 .height(availableHeight)
12190 .y(function(d) { return d.display.y + d.display.y0 })
12192 .color(data.map(function(d,i) {
12193 return d.color || color(d, d.seriesIndex);
12196 var scatterWrap = g.select('.nv-scatterWrap')
12199 scatterWrap.call(scatter);
12201 defsEnter.append('clipPath')
12202 .attr('id', 'nv-edge-clip-' + id)
12205 wrap.select('#nv-edge-clip-' + id + ' rect')
12206 .attr('width', availableWidth)
12207 .attr('height', availableHeight);
12209 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
12211 var area = d3.svg.area()
12212 .x(function(d,i) { return x(getX(d,i)) })
12214 return y(d.display.y0)
12217 return y(d.display.y + d.display.y0)
12219 .interpolate(interpolate);
12221 var zeroArea = d3.svg.area()
12222 .x(function(d,i) { return x(getX(d,i)) })
12223 .y0(function(d) { return y(d.display.y0) })
12224 .y1(function(d) { return y(d.display.y0) });
12226 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
12227 .data(function(d) { return d });
12229 path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
12230 .attr('d', function(d,i){
12231 return zeroArea(d.values, d.seriesIndex);
12233 .on('mouseover', function(d,i) {
12234 d3.select(this).classed('hover', true);
12235 dispatch.areaMouseover({
12238 pos: [d3.event.pageX, d3.event.pageY],
12239 seriesIndex: d.seriesIndex
12242 .on('mouseout', function(d,i) {
12243 d3.select(this).classed('hover', false);
12244 dispatch.areaMouseout({
12247 pos: [d3.event.pageX, d3.event.pageY],
12248 seriesIndex: d.seriesIndex
12251 .on('click', function(d,i) {
12252 d3.select(this).classed('hover', false);
12253 dispatch.areaClick({
12256 pos: [d3.event.pageX, d3.event.pageY],
12257 seriesIndex: d.seriesIndex
12261 path.exit().remove();
12262 path.style('fill', function(d,i){
12263 return d.color || color(d, d.seriesIndex)
12265 .style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
12266 path.watchTransition(renderWatch,'stackedArea path')
12267 .attr('d', function(d,i) {
12268 return area(d.values,i)
12271 //============================================================
12272 // Event Handling/Dispatching (in chart's scope)
12273 //------------------------------------------------------------
12275 scatter.dispatch.on('elementMouseover.area', function(e) {
12276 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
12278 scatter.dispatch.on('elementMouseout.area', function(e) {
12279 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
12282 //Special offset functions
12283 chart.d3_stackedOffset_stackPercent = function(stackData) {
12284 var n = stackData.length, //How many series
12285 m = stackData[0].length, //how many points per series
12291 for (j = 0; j < m; ++j) { //Looping through all points
12292 for (i = 0, o = 0; i < dataRaw.length; i++) { //looping through all series
12293 o += getY(dataRaw[i].values[j]); //total y value of all series at a certian point in time.
12296 if (o) for (i = 0; i < n; i++) { //(total y value of all series at point in time i) != 0
12297 stackData[i][j][1] /= o;
12298 } else { //(total y value of all series at point in time i) == 0
12299 for (i = 0; i < n; i++) {
12300 stackData[i][j][1] = 0;
12304 for (j = 0; j < m; ++j) y0[j] = 0;
12310 renderWatch.renderEnd('stackedArea immediate');
12314 //============================================================
12315 // Global getters and setters
12316 //------------------------------------------------------------
12318 chart.dispatch = dispatch;
12319 chart.scatter = scatter;
12321 scatter.dispatch.on('elementClick', function(){ dispatch.elementClick.apply(this, arguments); });
12322 scatter.dispatch.on('elementMouseover', function(){ dispatch.elementMouseover.apply(this, arguments); });
12323 scatter.dispatch.on('elementMouseout', function(){ dispatch.elementMouseout.apply(this, arguments); });
12325 chart.interpolate = function(_) {
12326 if (!arguments.length) return interpolate;
12331 chart.duration = function(_) {
12332 if (!arguments.length) return duration;
12334 renderWatch.reset(duration);
12335 scatter.duration(duration);
12339 chart.dispatch = dispatch;
12340 chart.scatter = scatter;
12341 chart.options = nv.utils.optionsFunc.bind(chart);
12343 chart._options = Object.create({}, {
12344 // simple options, just get/set the necessary values
12345 width: {get: function(){return width;}, set: function(_){width=_;}},
12346 height: {get: function(){return height;}, set: function(_){height=_;}},
12347 clipEdge: {get: function(){return clipEdge;}, set: function(_){clipEdge=_;}},
12348 offset: {get: function(){return offset;}, set: function(_){offset=_;}},
12349 order: {get: function(){return order;}, set: function(_){order=_;}},
12350 interpolate: {get: function(){return interpolate;}, set: function(_){interpolate=_;}},
12352 // simple functor options
12353 x: {get: function(){return getX;}, set: function(_){getX = d3.functor(_);}},
12354 y: {get: function(){return getY;}, set: function(_){getY = d3.functor(_);}},
12356 // options that require extra logic in the setter
12357 margin: {get: function(){return margin;}, set: function(_){
12358 margin.top = _.top !== undefined ? _.top : margin.top;
12359 margin.right = _.right !== undefined ? _.right : margin.right;
12360 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
12361 margin.left = _.left !== undefined ? _.left : margin.left;
12363 color: {get: function(){return color;}, set: function(_){
12364 color = nv.utils.getColor(_);
12366 style: {get: function(){return style;}, set: function(_){
12370 chart.offset('zero');
12371 chart.order('default');
12374 chart.offset('wiggle');
12375 chart.order('inside-out');
12377 case 'stream-center':
12378 chart.offset('silhouette');
12379 chart.order('inside-out');
12382 chart.offset('expand');
12383 chart.order('default');
12385 case 'stack_percent':
12386 chart.offset(chart.d3_stackedOffset_stackPercent);
12387 chart.order('default');
12391 duration: {get: function(){return duration;}, set: function(_){
12393 renderWatch.reset(duration);
12394 scatter.duration(duration);
12398 nv.utils.inheritOptions(chart, scatter);
12399 nv.utils.initOptions(chart);
12404 nv.models.stackedAreaChart = function() {
12407 //============================================================
12408 // Public Variables with Default Settings
12409 //------------------------------------------------------------
12411 var stacked = nv.models.stackedArea()
12412 , xAxis = nv.models.axis()
12413 , yAxis = nv.models.axis()
12414 , legend = nv.models.legend()
12415 , controls = nv.models.legend()
12416 , interactiveLayer = nv.interactiveGuideline()
12417 , tooltip = nv.models.tooltip()
12420 var margin = {top: 30, right: 25, bottom: 50, left: 60}
12423 , color = nv.utils.defaultColor()
12424 , showControls = true
12425 , showLegend = true
12428 , rightAlignYAxis = false
12429 , useInteractiveGuideline = false
12430 , x //can be accessed via chart.xScale()
12431 , y //can be accessed via chart.yScale()
12432 , state = nv.utils.state()
12433 , defaultState = null
12435 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
12436 , controlWidth = 250
12437 , controlOptions = ['Stacked','Stream','Expanded']
12438 , controlLabels = {}
12442 state.style = stacked.style();
12443 xAxis.orient('bottom').tickPadding(7);
12444 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
12447 .headerFormatter(function(d, i) {
12448 return xAxis.tickFormat()(d, i);
12450 .valueFormatter(function(d, i) {
12451 return yAxis.tickFormat()(d, i);
12454 interactiveLayer.tooltip
12455 .headerFormatter(function(d, i) {
12456 return xAxis.tickFormat()(d, i);
12458 .valueFormatter(function(d, i) {
12459 return yAxis.tickFormat()(d, i);
12462 var oldYTickFormat = null,
12463 oldValueFormatter = null;
12465 controls.updateState(false);
12467 //============================================================
12468 // Private Variables
12469 //------------------------------------------------------------
12471 var renderWatch = nv.utils.renderWatch(dispatch);
12472 var style = stacked.style();
12474 var stateGetter = function(data) {
12477 active: data.map(function(d) { return !d.disabled }),
12478 style: stacked.style()
12483 var stateSetter = function(data) {
12484 return function(state) {
12485 if (state.style !== undefined)
12486 style = state.style;
12487 if (state.active !== undefined)
12488 data.forEach(function(series,i) {
12489 series.disabled = !state.active[i];
12494 var percentFormatter = d3.format('%');
12496 function chart(selection) {
12497 renderWatch.reset();
12498 renderWatch.models(stacked);
12499 if (showXAxis) renderWatch.models(xAxis);
12500 if (showYAxis) renderWatch.models(yAxis);
12502 selection.each(function(data) {
12503 var container = d3.select(this),
12505 nv.utils.initSVG(container);
12507 var availableWidth = nv.utils.availableWidth(width, container, margin),
12508 availableHeight = nv.utils.availableHeight(height, container, margin);
12510 chart.update = function() { container.transition().duration(duration).call(chart); };
12511 chart.container = this;
12514 .setter(stateSetter(data), chart.update)
12515 .getter(stateGetter(data))
12518 // DEPRECATED set state.disabled
12519 state.disabled = data.map(function(d) { return !!d.disabled });
12521 if (!defaultState) {
12524 for (key in state) {
12525 if (state[key] instanceof Array)
12526 defaultState[key] = state[key].slice(0);
12528 defaultState[key] = state[key];
12532 // Display No Data message if there's nothing to show.
12533 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12534 nv.utils.noData(chart, container)
12537 container.selectAll('.nv-noData').remove();
12541 x = stacked.xScale();
12542 y = stacked.yScale();
12544 // Setup containers and skeleton of chart
12545 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
12546 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
12547 var g = wrap.select('g');
12549 gEnter.append("rect").style("opacity",0);
12550 gEnter.append('g').attr('class', 'nv-x nv-axis');
12551 gEnter.append('g').attr('class', 'nv-y nv-axis');
12552 gEnter.append('g').attr('class', 'nv-stackedWrap');
12553 gEnter.append('g').attr('class', 'nv-legendWrap');
12554 gEnter.append('g').attr('class', 'nv-controlsWrap');
12555 gEnter.append('g').attr('class', 'nv-interactive');
12557 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
12561 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
12563 legend.width(legendWidth);
12564 g.select('.nv-legendWrap').datum(data).call(legend);
12566 if ( margin.top != legend.height()) {
12567 margin.top = legend.height();
12568 availableHeight = nv.utils.availableHeight(height, container, margin);
12571 g.select('.nv-legendWrap')
12572 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
12576 if (showControls) {
12577 var controlsData = [
12579 key: controlLabels.stacked || 'Stacked',
12580 metaKey: 'Stacked',
12581 disabled: stacked.style() != 'stack',
12585 key: controlLabels.stream || 'Stream',
12587 disabled: stacked.style() != 'stream',
12591 key: controlLabels.expanded || 'Expanded',
12592 metaKey: 'Expanded',
12593 disabled: stacked.style() != 'expand',
12597 key: controlLabels.stack_percent || 'Stack %',
12598 metaKey: 'Stack_Percent',
12599 disabled: stacked.style() != 'stack_percent',
12600 style: 'stack_percent'
12604 controlWidth = (controlOptions.length/3) * 260;
12605 controlsData = controlsData.filter(function(d) {
12606 return controlOptions.indexOf(d.metaKey) !== -1;
12610 .width( controlWidth )
12611 .color(['#444', '#444', '#444']);
12613 g.select('.nv-controlsWrap')
12614 .datum(controlsData)
12617 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
12618 margin.top = Math.max(controls.height(), legend.height());
12619 availableHeight = nv.utils.availableHeight(height, container, margin);
12622 g.select('.nv-controlsWrap')
12623 .attr('transform', 'translate(0,' + (-margin.top) +')');
12626 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12628 if (rightAlignYAxis) {
12629 g.select(".nv-y.nv-axis")
12630 .attr("transform", "translate(" + availableWidth + ",0)");
12633 //Set up interactive layer
12634 if (useInteractiveGuideline) {
12636 .width(availableWidth)
12637 .height(availableHeight)
12638 .margin({left: margin.left, top: margin.top})
12639 .svgContainer(container)
12641 wrap.select(".nv-interactive").call(interactiveLayer);
12645 .width(availableWidth)
12646 .height(availableHeight);
12648 var stackedWrap = g.select('.nv-stackedWrap')
12651 stackedWrap.transition().call(stacked);
12656 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
12657 .tickSize( -availableHeight, 0);
12659 g.select('.nv-x.nv-axis')
12660 .attr('transform', 'translate(0,' + availableHeight + ')');
12662 g.select('.nv-x.nv-axis')
12663 .transition().duration(0)
12669 if (stacked.offset() === 'wiggle') {
12673 ticks = nv.utils.calcTicksY(availableHeight/36, data);
12677 .tickSize(-availableWidth, 0);
12679 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
12680 var currentFormat = yAxis.tickFormat();
12682 if ( !oldYTickFormat || currentFormat !== percentFormatter )
12683 oldYTickFormat = currentFormat;
12685 //Forces the yAxis to use percentage in 'expand' mode.
12686 yAxis.tickFormat(percentFormatter);
12689 if (oldYTickFormat) {
12690 yAxis.tickFormat(oldYTickFormat);
12691 oldYTickFormat = null;
12695 g.select('.nv-y.nv-axis')
12696 .transition().duration(0)
12700 //============================================================
12701 // Event Handling/Dispatching (in chart's scope)
12702 //------------------------------------------------------------
12704 stacked.dispatch.on('areaClick.toggle', function(e) {
12705 if (data.filter(function(d) { return !d.disabled }).length === 1)
12706 data.forEach(function(d) {
12707 d.disabled = false;
12710 data.forEach(function(d,i) {
12711 d.disabled = (i != e.seriesIndex);
12714 state.disabled = data.map(function(d) { return !!d.disabled });
12715 dispatch.stateChange(state);
12720 legend.dispatch.on('stateChange', function(newState) {
12721 for (var key in newState)
12722 state[key] = newState[key];
12723 dispatch.stateChange(state);
12727 controls.dispatch.on('legendClick', function(d,i) {
12728 if (!d.disabled) return;
12730 controlsData = controlsData.map(function(s) {
12734 d.disabled = false;
12736 stacked.style(d.style);
12739 state.style = stacked.style();
12740 dispatch.stateChange(state);
12745 interactiveLayer.dispatch.on('elementMousemove', function(e) {
12746 stacked.clearHighlights();
12747 var singlePoint, pointIndex, pointXLocation, allData = [];
12749 .filter(function(series, i) {
12750 series.seriesIndex = i;
12751 return !series.disabled;
12753 .forEach(function(series,i) {
12754 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
12755 var point = series.values[pointIndex];
12756 var pointYValue = chart.y()(point, pointIndex);
12757 if (pointYValue != null) {
12758 stacked.highlightPoint(i, pointIndex, true);
12760 if (typeof point === 'undefined') return;
12761 if (typeof singlePoint === 'undefined') singlePoint = point;
12762 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
12764 //If we are in 'expand' mode, use the stacked percent value instead of raw value.
12765 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
12768 value: tooltipValue,
12769 color: color(series,series.seriesIndex),
12770 stackedValue: point.display
12776 //Highlight the tooltip entry based on which stack the mouse is closest to.
12777 if (allData.length > 2) {
12778 var yValue = chart.yScale().invert(e.mouseY);
12779 var yDistMax = Infinity, indexToHighlight = null;
12780 allData.forEach(function(series,i) {
12782 //To handle situation where the stacked area chart is negative, we need to use absolute values
12783 //when checking if the mouse Y value is within the stack area.
12784 yValue = Math.abs(yValue);
12785 var stackedY0 = Math.abs(series.stackedValue.y0);
12786 var stackedY = Math.abs(series.stackedValue.y);
12787 if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
12789 indexToHighlight = i;
12793 if (indexToHighlight != null)
12794 allData[indexToHighlight].highlight = true;
12797 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
12799 var valueFormatter = interactiveLayer.tooltip.valueFormatter();
12800 // Keeps track of the tooltip valueFormatter if the chart changes to expanded view
12801 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
12802 if ( !oldValueFormatter ) {
12803 oldValueFormatter = valueFormatter;
12805 //Forces the tooltip to use percentage in 'expand' mode.
12806 valueFormatter = d3.format(".1%");
12809 if (oldValueFormatter) {
12810 valueFormatter = oldValueFormatter;
12811 oldValueFormatter = null;
12815 interactiveLayer.tooltip
12816 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
12817 .chartContainer(that.parentNode)
12818 .valueFormatter(valueFormatter)
12826 interactiveLayer.renderGuideLine(pointXLocation);
12830 interactiveLayer.dispatch.on("elementMouseout",function(e) {
12831 stacked.clearHighlights();
12834 // Update chart from a state object passed to event handler
12835 dispatch.on('changeState', function(e) {
12837 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
12838 data.forEach(function(series,i) {
12839 series.disabled = e.disabled[i];
12842 state.disabled = e.disabled;
12845 if (typeof e.style !== 'undefined') {
12846 stacked.style(e.style);
12855 renderWatch.renderEnd('stacked Area chart immediate');
12859 //============================================================
12860 // Event Handling/Dispatching (out of chart's scope)
12861 //------------------------------------------------------------
12863 stacked.dispatch.on('elementMouseover.tooltip', function(evt) {
12864 evt.point['x'] = stacked.x()(evt.point);
12865 evt.point['y'] = stacked.y()(evt.point);
12866 tooltip.data(evt).position(evt.pos).hidden(false);
12869 stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
12870 tooltip.hidden(true)
12873 //============================================================
12874 // Expose Public Variables
12875 //------------------------------------------------------------
12877 // expose chart's sub-components
12878 chart.dispatch = dispatch;
12879 chart.stacked = stacked;
12880 chart.legend = legend;
12881 chart.controls = controls;
12882 chart.xAxis = xAxis;
12883 chart.yAxis = yAxis;
12884 chart.interactiveLayer = interactiveLayer;
12885 chart.tooltip = tooltip;
12887 chart.dispatch = dispatch;
12888 chart.options = nv.utils.optionsFunc.bind(chart);
12890 chart._options = Object.create({}, {
12891 // simple options, just get/set the necessary values
12892 width: {get: function(){return width;}, set: function(_){width=_;}},
12893 height: {get: function(){return height;}, set: function(_){height=_;}},
12894 showLegend: {get: function(){return showLegend;}, set: function(_){showLegend=_;}},
12895 showXAxis: {get: function(){return showXAxis;}, set: function(_){showXAxis=_;}},
12896 showYAxis: {get: function(){return showYAxis;}, set: function(_){showYAxis=_;}},
12897 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
12898 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
12899 showControls: {get: function(){return showControls;}, set: function(_){showControls=_;}},
12900 controlLabels: {get: function(){return controlLabels;}, set: function(_){controlLabels=_;}},
12901 controlOptions: {get: function(){return controlOptions;}, set: function(_){controlOptions=_;}},
12903 // deprecated options
12904 tooltips: {get: function(){return tooltip.enabled();}, set: function(_){
12905 // deprecated after 1.7.1
12906 nv.deprecated('tooltips', 'use chart.tooltip.enabled() instead');
12907 tooltip.enabled(!!_);
12909 tooltipContent: {get: function(){return tooltip.contentGenerator();}, set: function(_){
12910 // deprecated after 1.7.1
12911 nv.deprecated('tooltipContent', 'use chart.tooltip.contentGenerator() instead');
12912 tooltip.contentGenerator(_);
12915 // options that require extra logic in the setter
12916 margin: {get: function(){return margin;}, set: function(_){
12917 margin.top = _.top !== undefined ? _.top : margin.top;
12918 margin.right = _.right !== undefined ? _.right : margin.right;
12919 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
12920 margin.left = _.left !== undefined ? _.left : margin.left;
12922 duration: {get: function(){return duration;}, set: function(_){
12924 renderWatch.reset(duration);
12925 stacked.duration(duration);
12926 xAxis.duration(duration);
12927 yAxis.duration(duration);
12929 color: {get: function(){return color;}, set: function(_){
12930 color = nv.utils.getColor(_);
12931 legend.color(color);
12932 stacked.color(color);
12934 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
12935 rightAlignYAxis = _;
12936 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
12938 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
12939 useInteractiveGuideline = !!_;
12940 chart.interactive(!_);
12941 chart.useVoronoi(!_);
12942 stacked.scatter.interactive(!_);
12946 nv.utils.inheritOptions(chart, stacked);
12947 nv.utils.initOptions(chart);
12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
12952 nv.models.sunburst = function() {
12955 //============================================================
12956 // Public Variables with Default Settings
12957 //------------------------------------------------------------
12959 var margin = {top: 0, right: 0, bottom: 0, left: 0}
12963 , modes = {count: function(d) { return 1; }, size: function(d) { return d.size }}
12964 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
12966 , color = nv.utils.defaultColor()
12968 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMousemove', 'elementMouseover', 'elementMouseout', 'renderEnd')
12971 var x = d3.scale.linear().range([0, 2 * Math.PI]);
12972 var y = d3.scale.sqrt();
12974 var partition = d3.layout.partition()
12976 .value(function(d) { return 1; });
12978 var arc = d3.svg.arc()
12979 .startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
12980 .endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
12981 .innerRadius(function(d) { return Math.max(0, y(d.y)); })
12982 .outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
12984 // Keep track of the current and previous node being displayed as the root.
12985 var node, prevNode;
12986 // Keep track of the root node
12989 //============================================================
12991 //------------------------------------------------------------
12993 var renderWatch = nv.utils.renderWatch(dispatch);
12995 function chart(selection) {
12996 renderWatch.reset();
12997 selection.each(function(data) {
12998 container = d3.select(this);
12999 var availableWidth = nv.utils.availableWidth(width, container, margin);
13000 var availableHeight = nv.utils.availableHeight(height, container, margin);
13001 var radius = Math.min(availableWidth, availableHeight) / 2;
13004 nv.utils.initSVG(container);
13006 // Setup containers and skeleton of chart
13007 var wrap = container.selectAll('.nv-wrap.nv-sunburst').data(data);
13008 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburst nv-chart-' + id);
13010 var g = wrapEnter.selectAll('nv-sunburst');
13012 wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
13014 container.on('click', function (d, i) {
13015 dispatch.chartClick({
13023 y.range([0, radius]);
13025 node = node || data;
13026 rootNode = data[0];
13027 partition.value(modes[mode] || modes["count"]);
13028 path = g.data(partition.nodes).enter()
13031 .style("fill", function (d) {
13032 return color((d.children ? d : d.parent).name);
13034 .style("stroke", "#FFF")
13035 .on("click", function(d) {
13036 if (prevNode !== node && node !== d) prevNode = node;
13039 .duration(duration)
13040 .attrTween("d", arcTweenZoom(d));
13043 .on("dblclick", function(d) {
13044 if (prevNode.parent == d) {
13046 .duration(duration)
13047 .attrTween("d", arcTweenZoom(rootNode));
13051 .on('mouseover', function(d,i){
13052 d3.select(this).classed('hover', true).style('opacity', 0.8);
13053 dispatch.elementMouseover({
13055 color: d3.select(this).style("fill")
13058 .on('mouseout', function(d,i){
13059 d3.select(this).classed('hover', false).style('opacity', 1);
13060 dispatch.elementMouseout({
13064 .on('mousemove', function(d,i){
13065 dispatch.elementMousemove({
13072 // Setup for switching data: stash the old values for transition.
13073 function stash(d) {
13078 // When switching data: interpolate the arcs in data space.
13079 function arcTweenData(a, i) {
13080 var oi = d3.interpolate({x: a.x0, dx: a.dx0}, a);
13082 function tween(t) {
13090 // If we are on the first arc, adjust the x domain to match the root node
13091 // at the current zoom level. (We only need to do this once.)
13092 var xd = d3.interpolate(x.domain(), [node.x, node.x + node.dx]);
13093 return function (t) {
13102 // When zooming: interpolate the scales.
13103 function arcTweenZoom(d) {
13104 var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
13105 yd = d3.interpolate(y.domain(), [d.y, 1]),
13106 yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
13107 return function (d, i) {
13114 y.domain(yd(t)).range(yr(t));
13122 renderWatch.renderEnd('sunburst immediate');
13126 //============================================================
13127 // Expose Public Variables
13128 //------------------------------------------------------------
13130 chart.dispatch = dispatch;
13131 chart.options = nv.utils.optionsFunc.bind(chart);
13133 chart._options = Object.create({}, {
13134 // simple options, just get/set the necessary values
13135 width: {get: function(){return width;}, set: function(_){width=_;}},
13136 height: {get: function(){return height;}, set: function(_){height=_;}},
13137 mode: {get: function(){return mode;}, set: function(_){mode=_;}},
13138 id: {get: function(){return id;}, set: function(_){id=_;}},
13139 duration: {get: function(){return duration;}, set: function(_){duration=_;}},
13141 // options that require extra logic in the setter
13142 margin: {get: function(){return margin;}, set: function(_){
13143 margin.top = _.top != undefined ? _.top : margin.top;
13144 margin.right = _.right != undefined ? _.right : margin.right;
13145 margin.bottom = _.bottom != undefined ? _.bottom : margin.bottom;
13146 margin.left = _.left != undefined ? _.left : margin.left;
13148 color: {get: function(){return color;}, set: function(_){
13149 color=nv.utils.getColor(_);
13153 nv.utils.initOptions(chart);
13156 nv.models.sunburstChart = function() {
13159 //============================================================
13160 // Public Variables with Default Settings
13161 //------------------------------------------------------------
13163 var sunburst = nv.models.sunburst();
13164 var tooltip = nv.models.tooltip();
13166 var margin = {top: 30, right: 20, bottom: 20, left: 20}
13169 , color = nv.utils.defaultColor()
13170 , id = Math.round(Math.random() * 100000)
13171 , defaultState = null
13174 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
13177 //============================================================
13178 // Private Variables
13179 //------------------------------------------------------------
13181 var renderWatch = nv.utils.renderWatch(dispatch);
13182 tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) {
13186 //============================================================
13188 //------------------------------------------------------------
13190 function chart(selection) {
13191 renderWatch.reset();
13192 renderWatch.models(sunburst);
13194 selection.each(function(data) {
13195 var container = d3.select(this);
13196 nv.utils.initSVG(container);
13199 var availableWidth = nv.utils.availableWidth(width, container, margin),
13200 availableHeight = nv.utils.availableHeight(height, container, margin);
13202 chart.update = function() {
13203 if (duration === 0)
13204 container.call(chart);
13206 container.transition().duration(duration).call(chart)
13208 chart.container = this;
13210 // Display No Data message if there's nothing to show.
13211 if (!data || !data.length) {
13212 nv.utils.noData(chart, container);
13215 container.selectAll('.nv-noData').remove();
13218 // Setup containers and skeleton of chart
13219 var wrap = container.selectAll('g.nv-wrap.nv-sunburstChart').data(data);
13220 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sunburstChart').append('g');
13221 var g = wrap.select('g');
13223 gEnter.append('g').attr('class', 'nv-sunburstWrap');
13225 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13227 // Main Chart Component(s)
13228 sunburst.width(availableWidth).height(availableHeight);
13229 var sunWrap = g.select('.nv-sunburstWrap').datum(data);
13230 d3.transition(sunWrap).call(sunburst);
13234 renderWatch.renderEnd('sunburstChart immediate');
13238 //============================================================
13239 // Event Handling/Dispatching (out of chart's scope)
13240 //------------------------------------------------------------
13242 sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
13244 key: evt.data.name,
13245 value: evt.data.size,
13248 tooltip.data(evt).hidden(false);
13251 sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
13252 tooltip.hidden(true);
13255 sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
13256 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
13259 //============================================================
13260 // Expose Public Variables
13261 //------------------------------------------------------------
13263 // expose chart's sub-components
13264 chart.dispatch = dispatch;
13265 chart.sunburst = sunburst;
13266 chart.tooltip = tooltip;
13267 chart.options = nv.utils.optionsFunc.bind(chart);
13269 // use Object get/set functionality to map between vars and chart functions
13270 chart._options = Object.create({}, {
13271 // simple options, just get/set the necessary values
13272 noData: {get: function(){return noData;}, set: function(_){noData=_;}},
13273 defaultState: {get: function(){return defaultState;}, set: function(_){defaultState=_;}},
13275 // options that require extra logic in the setter
13276 color: {get: function(){return color;}, set: function(_){
13278 sunburst.color(color);
13280 duration: {get: function(){return duration;}, set: function(_){
13282 renderWatch.reset(duration);
13283 sunburst.duration(duration);
13285 margin: {get: function(){return margin;}, set: function(_){
13286 margin.top = _.top !== undefined ? _.top : margin.top;
13287 margin.right = _.right !== undefined ? _.right : margin.right;
13288 margin.bottom = _.bottom !== undefined ? _.bottom : margin.bottom;
13289 margin.left = _.left !== undefined ? _.left : margin.left;
13292 nv.utils.inheritOptions(chart, sunburst);
13293 nv.utils.initOptions(chart);
13297 nv.version = "1.8.1";