view gtc/nv.d3.js @ 144:90f3021e3137

myrss2: FEEDS: Remove longform.org; add propublic.org
author paulo
date Tue, 28 May 2024 06:23:58 +0000
parents
children
line source
1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
2 (function(){
4 // set up main nv object
5 var nv = {};
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");
28 }
30 var aArgs = Array.prototype.slice.call(arguments, 1),
31 fToBind = this,
32 fNOP = function () {},
33 fBound = function () {
34 return fToBind.apply(this instanceof fNOP && oThis
35 ? this
36 : oThis,
37 aArgs.concat(Array.prototype.slice.call(arguments)));
38 };
40 fNOP.prototype = this.prototype;
41 fBound.prototype = new fNOP();
42 return fBound;
43 };
44 }
46 // Development render timers - disabled if dev = false
47 if (nv.dev) {
48 nv.dispatch.on('render_start', function(e) {
49 nv.logs.startTime = +new Date();
50 });
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
56 });
57 }
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.
63 nv.log = function() {
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);
69 }
70 return arguments[arguments.length - 1];
71 };
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 || '');
77 }
78 };
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
85 step = step || 1;
87 nv.render.active = true;
88 nv.dispatch.render_start();
90 var renderLoop = function() {
91 var chart, graph;
93 for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
94 chart = graph.generate();
95 if (typeof graph.callback == typeof(Function)) graph.callback(chart);
96 }
98 nv.render.queue.splice(0, i);
100 if (nv.render.queue.length) {
101 setTimeout(renderLoop);
102 }
103 else {
104 nv.dispatch.render_end();
105 nv.render.active = false;
106 }
107 };
109 setTimeout(renderLoop);
110 };
112 nv.render.active = false;
113 nv.render.queue = [];
115 /*
116 Adds a chart to the async rendering queue. This method can take arguments in two forms:
117 nv.addGraph({
118 generate: <Function>
119 callback: <Function>
120 })
122 or
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.
131 */
132 nv.addGraph = function(obj) {
133 if (typeof arguments[0] === typeof(Function)) {
134 obj = {generate: arguments[0], callback: arguments[1]};
135 }
137 nv.render.queue.push(obj);
139 if (!nv.render.active) {
140 nv.render();
141 }
142 };
144 // Node/CommonJS exports
145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
146 module.exports = nv;
147 }
149 if (typeof(window) !== 'undefined') {
150 window.nv = nv;
151 }
152 /* Facade for queueing DOM write operations
153 * with Fastdom (https://github.com/wilsonpage/fastdom)
154 * if available.
155 * This could easily be extended to support alternate
156 * implementations in the future.
157 */
158 nv.dom.write = function(callback) {
159 if (window.fastdom !== undefined) {
160 return fastdom.write(callback);
161 }
162 return callback();
163 };
165 /* Facade for queueing DOM read operations
166 * with Fastdom (https://github.com/wilsonpage/fastdom)
167 * if available.
168 * This could easily be extended to support alternate
169 * implementations in the future.
170 */
171 nv.dom.read = function(callback) {
172 if (window.fastdom !== undefined) {
173 return fastdom.read(callback);
174 }
175 return callback();
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.
183 */
184 nv.interactiveGuideline = function() {
185 "use strict";
187 var tooltip = nv.models.tooltip();
188 tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false);
190 //Public settings
191 var width = null;
192 var height = null;
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")
213 .data([data]);
214 var wrapEnter = wrap.enter()
215 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
216 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
218 if (!svgContainer) {
219 return;
220 }
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;
228 if (isMSIE) {
229 /*
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.
235 */
236 mouseX = d3.event.offsetX;
237 mouseY = d3.event.offsetY;
239 /*
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
243 is located.
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.
247 */
248 if(d3.event.target.tagName !== "svg") {
249 subtractMargin = false;
250 }
252 if (d3.event.target.className.baseVal.match("nv-legend")) {
253 mouseOutAnyReason = true;
254 }
256 }
258 if(subtractMargin) {
259 mouseX -= margin.left;
260 mouseY -= margin.top;
261 }
263 /* If mouseX/Y is outside of the chart's bounds,
264 trigger a mouseOut event.
265 */
266 if (mouseX < 0 || mouseY < 0
267 || mouseX > availableWidth || mouseY > availableHeight
268 || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
269 || mouseOutAnyReason
270 ) {
272 if (isMSIE) {
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))) {
278 return;
279 }
280 }
281 dispatch.elementMouseout({
282 mouseX: mouseX,
283 mouseY: mouseY
284 });
285 layer.renderGuideLine(null); //hide the guideline
286 tooltip.hidden(true);
287 return;
288 } else {
289 tooltip.hidden(false);
290 }
292 var pointXValue = xScale.invert(mouseX);
293 dispatch.elementMousemove({
294 mouseX: mouseX,
295 mouseY: mouseY,
296 pointXValue: pointXValue
297 });
299 //If user double clicks the layer, fire a elementDblclick
300 if (d3.event.type === "dblclick") {
301 dispatch.elementDblclick({
302 mouseX: mouseX,
303 mouseY: mouseY,
304 pointXValue: pointXValue
305 });
306 }
308 // if user single clicks the layer, fire elementClick
309 if (d3.event.type === 'click') {
310 dispatch.elementClick({
311 mouseX: mouseX,
312 mouseY: mouseY,
313 pointXValue: pointXValue
314 });
315 }
316 }
318 svgContainer
319 .on("touchmove",mouseHandler)
320 .on("mousemove",mouseHandler, true)
321 .on("mouseout" ,mouseHandler,true)
322 .on("dblclick" ,mouseHandler)
323 .on("click", mouseHandler)
324 ;
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")
333 .selectAll("line")
334 .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
335 line.enter()
336 .append("line")
337 .attr("class", "nv-guideline")
338 .attr("x1", function(d) { return d;})
339 .attr("x2", function(d) { return d;})
340 .attr("y1", availableHeight)
341 .attr("y2",0);
342 line.exit().remove();
343 });
344 }
345 });
346 }
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;
355 return layer;
356 };
358 layer.width = function(_) {
359 if (!arguments.length) return width;
360 width = _;
361 return layer;
362 };
364 layer.height = function(_) {
365 if (!arguments.length) return height;
366 height = _;
367 return layer;
368 };
370 layer.xScale = function(_) {
371 if (!arguments.length) return xScale;
372 xScale = _;
373 return layer;
374 };
376 layer.showGuideLine = function(_) {
377 if (!arguments.length) return showGuideLine;
378 showGuideLine = _;
379 return layer;
380 };
382 layer.svgContainer = function(_) {
383 if (!arguments.length) return svgContainer;
384 svgContainer = _;
385 return layer;
386 };
388 return layer;
389 };
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.
403 */
404 nv.interactiveBisect = function (values, searchVal, xAccessor) {
405 "use strict";
406 if (! (values instanceof Array)) {
407 return null;
408 }
409 var _xAccessor;
410 if (typeof xAccessor !== 'function') {
411 _xAccessor = function(d) {
412 return d.x;
413 }
414 } else {
415 _xAccessor = xAccessor;
416 }
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.
420 //
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;
428 };
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;
436 }
438 if (currentValue === searchVal) {
439 return index; //found exact match
440 }
442 var nextIndex = d3.min([index+1, values.length - 1]);
443 var nextValue = _xAccessor(values[nextIndex]);
445 if (typeof nextValue === 'undefined') {
446 nextValue = nextIndex;
447 }
449 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
450 return index;
451 } else {
452 return nextIndex
453 }
454 };
456 /*
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.
460 */
461 nv.nearestValueIndex = function (values, searchVal, threshold) {
462 "use strict";
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) {
467 yDistMax = delta;
468 indexToHighlight = i;
469 }
470 });
471 return indexToHighlight;
472 };
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.
478 */
479 (function() {
480 "use strict";
482 /* Model which can be instantiated to handle tooltip rendering.
483 Example usage:
484 var tip = nv.models.tooltip().gravity('w').distance(23)
485 .data(myDataObject);
487 tip(); //just invoke the returned function to render tooltip.
488 */
489 nv.models.tooltip = function() {
491 /*
492 Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
493 Example Format of data:
494 {
495 key: "Date",
496 value: "August 2009",
497 series: [
498 {key: "Series 1", value: "Value 1", color: "#000"},
499 {key: "Series 2", value: "Value 2", color: "#00f"}
500 ]
501 }
502 */
503 var data = null;
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
519 ;
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) {
534 return d;
535 };
537 //Format function for the tooltip header value.
538 var headerFormatter = function(d) {
539 return d;
540 };
542 var keyFormatter = function(d, i) {
543 return d;
544 };
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) {
549 if (d === null) {
550 return '';
551 }
553 var table = d3.select(document.createElement("table"));
554 if (headerEnabled) {
555 var theadEnter = table.selectAll("thead")
556 .data([d])
557 .enter().append("thead");
559 theadEnter.append("tr")
560 .append("td")
561 .attr("colspan", 3)
562 .append("strong")
563 .classed("x-value", true)
564 .html(headerFormatter(d.value));
565 }
567 var tbodyEnter = table.selectAll("tbody")
568 .data([d])
569 .enter().append("tbody");
571 var trowEnter = tbodyEnter.selectAll("tr")
572 .data(function(p) { return p.series})
573 .enter()
574 .append("tr")
575 .classed("highlight", function(p) { return p.highlight});
577 trowEnter.append("td")
578 .classed("legend-color-guide",true)
579 .append("div")
580 .style("background-color", function(p) { return p.color});
582 trowEnter.append("td")
583 .classed("key",true)
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) {
592 if (p.highlight) {
593 var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
594 var opacity = 0.6;
595 d3.select(this)
596 .style("border-bottom-color", opacityScale(opacity))
597 .style("border-top-color", opacityScale(opacity))
598 ;
599 }
600 });
602 var html = table.node().outerHTML;
603 if (d.footer !== undefined)
604 html += "<div class='footer'>" + d.footer + "</div>";
605 return html;
607 };
609 var dataSeriesExists = function(d) {
610 if (d && d.series) {
611 if (d.series instanceof Array) {
612 return !!d.series.length;
613 }
614 // if object, it's okay just convert to array of the object
615 if (d.series instanceof Object) {
616 d.series = [d.series];
617 return true;
618 }
619 }
620 return false;
621 };
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,
633 left, top;
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 ) {
642 var offsetTop = top;
643 do {
644 if( !isNaN( Elem.offsetTop ) ) {
645 offsetTop += (Elem.offsetTop);
646 }
647 Elem = Elem.offsetParent;
648 } while( Elem );
649 return offsetTop;
650 };
651 var tooltipLeft = function ( Elem ) {
652 var offsetLeft = left;
653 do {
654 if( !isNaN( Elem.offsetLeft ) ) {
655 offsetLeft += (Elem.offsetLeft);
656 }
657 Elem = Elem.offsetParent;
658 } while( Elem );
659 return offsetLeft;
660 };
662 // calculate position based on gravity
663 var tLeft, tTop;
664 switch (gravity) {
665 case 'e':
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;
673 break;
674 case 'w':
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;
682 break;
683 case 'n':
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;
691 break;
692 case 's':
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;
700 break;
701 case 'none':
702 left = pos[0];
703 top = pos[1] - distance;
704 tLeft = tooltipLeft(tooltipElem);
705 tTop = tooltipTop(tooltipElem);
706 break;
707 }
709 // adjust tooltip offsets
710 left -= offset.left;
711 top -= offset.top;
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
724 if (hidden) {
725 tooltip
726 .transition()
727 .delay(hideDelay)
728 .duration(0)
729 .style('opacity', 0);
730 } else {
731 tooltip
732 .interrupt() // cancel running transitions
733 .transition()
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;
738 }, 'important')
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);
743 }
747 });
748 };
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");
756 }
757 var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
758 if (viewBox) {
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;
764 }
765 }
766 }
768 //Creates new tooltip container, or uses existing one on DOM.
769 function initTooltip() {
770 if (!tooltip) {
771 var body;
772 if (chartContainer) {
773 body = chartContainer;
774 } else {
775 body = document.body;
776 }
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"))
780 .attr("id", id);
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();
786 }
787 }
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 () {
800 initTooltip();
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);
805 if (newContent) {
806 tooltipElem.innerHTML = newContent;
807 }
809 if (chartContainer && isInteractiveLayer) {
810 nv.dom.read(function() {
811 var svgComp = chartContainer.getElementsByTagName("svg")[0];
812 var svgOffset = {left:0,top:0};
813 if (svgComp) {
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;
824 }
825 svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
826 svgOffset.left = Math.abs(svgBound.left - chartBound.left);
827 }
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;
836 }
837 calcTooltipPosition([left,top]);
838 });
839 } else {
840 calcTooltipPosition([left,top]);
841 }
842 });
844 return nvtooltip;
845 }
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;
874 }},
875 offset: {get: function(){return offset;}, set: function(_){
876 offset.left = _.left !== undefined ? _.left : offset.left;
877 offset.top = _.top !== undefined ? _.top : offset.top;
878 }},
879 hidden: {get: function(){return hidden;}, set: function(_){
880 if (hidden != _) {
881 hidden = !!_;
882 nvtooltip();
883 }
884 }},
885 data: {get: function(){return data;}, set: function(_){
886 // if showing a single data point, adjust data format with that
887 if (_.point) {
888 _.value = _.point.x;
889 _.series = _.series || {};
890 _.series.value = _.point.y;
891 _.series.color = _.point.color || _.series.color;
892 }
893 data = _;
894 }},
896 // read only properties
897 tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}},
898 id: {get: function(){return id;}, set: function(_){}}
899 });
901 nv.utils.initOptions(nvtooltip);
902 return nvtooltip;
903 };
905 })();
908 /*
909 Gets the browser window size
911 Returns object with height and width properties
912 */
913 nv.utils.windowSize = function() {
914 // Sane defaults
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;
921 return (size);
922 }
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;
931 return (size);
932 }
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;
938 return (size);
939 }
941 return (size);
942 };
944 /*
945 Binds callback function to run when window is resized
946 */
947 nv.utils.windowResize = function(handler) {
948 if (window.addEventListener) {
949 window.addEventListener('resize', handler);
950 } else {
951 nv.log("ERROR: Failed to bind to window.resize with: ", handler);
952 }
953 // return object with clear function to remove the single added callback.
954 return {
955 callback: handler,
956 clear: function() {
957 window.removeEventListener('resize', handler);
958 }
959 }
960 };
963 /*
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
968 */
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);
981 };
983 //if passed a function or scale, return it, or whatever it may be
984 //external libs, such as angularjs-nvd3-directives use this
985 } else {
986 //can't really help it if someone passes rubbish as color
987 return color;
988 }
989 };
992 /*
993 Default color chooser uses a color scale of 20 colors from D3
994 https://github.com/mbostock/d3/wiki/Ordinal-Scales#categorical-colors
995 */
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());
999 };
1002 /*
1003 Returns a color function that takes the result of 'getKey' for each series and
1004 looks for a corresponding color from the dictionary
1005 */
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];
1020 } else {
1021 // no match in dictionary, use a default color
1022 if (!defIndex) {
1023 // used all the default colors, start over
1024 defIndex = defaultColors.length;
1026 defIndex = defIndex - 1;
1027 return defaultColors[defIndex];
1029 };
1030 };
1033 /*
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
1037 */
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(),
1045 target);
1046 nv.utils.pjax(links, content);
1047 });
1048 };
1050 d3.selectAll(links).on("click", function() {
1051 history.pushState(this.href, this.textContent, this.href);
1052 load(this.href);
1053 d3.event.preventDefault();
1054 });
1056 d3.select(window).on("popstate", function() {
1057 if (d3.event.state) {
1058 load(d3.event.state);
1060 });
1061 };
1064 /*
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
1068 */
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;
1077 return 0;
1078 };
1081 /*
1082 Numbers that are undefined, null or NaN, convert them to zeros.
1083 */
1084 nv.utils.NaNtoZero = function(n) {
1085 if (typeof n !== 'number'
1086 || isNaN(n)
1087 || n === null
1088 || n === Infinity
1089 || n === -Infinity) {
1091 return 0;
1093 return n;
1094 };
1096 /*
1097 Add a way to watch for d3 transition ends to d3
1098 */
1099 d3.selection.prototype.watchTransition = function(renderWatch){
1100 var args = [this].concat([].slice.call(arguments, 1));
1101 return renderWatch.transition.apply(renderWatch, args);
1102 };
1105 /*
1106 Helper object to watch when d3 has rendered something
1107 */
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 = [];
1115 var self = this;
1117 this.models = function(models) {
1118 models = [].slice.call(arguments, 0);
1119 models.forEach(function(model){
1120 model.__rendered = false;
1121 (function(m){
1122 m.dispatch.on('renderEnd', function(arg){
1123 m.__rendered = true;
1124 self.renderEnd('model');
1125 });
1126 })(model);
1128 if (renderStack.indexOf(model) < 0) {
1129 renderStack.push(model);
1131 });
1132 return this;
1133 };
1135 this.reset = function(duration) {
1136 if (duration !== undefined) {
1137 _duration = duration;
1139 renderStack = [];
1140 };
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();
1147 } else {
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; };
1160 return selection;
1161 } else {
1162 if (selection.length === 0) {
1163 selection.__rendered = true;
1164 } else if (selection.every( function(d){ return !d.length; } )) {
1165 selection.__rendered = true;
1166 } else {
1167 selection.__rendered = false;
1170 var n = 0;
1171 return selection
1172 .transition()
1173 .duration(duration)
1174 .each(function(){ ++n; })
1175 .each('end', function(d, i) {
1176 if (--n === 0) {
1177 selection.__rendered = true;
1178 self.renderEnd.apply(this, args);
1180 });
1182 };
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);
1191 };
1194 /*
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}
1198 */
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]);
1209 } else {
1210 dst[key] = source[key];
1213 });
1214 };
1217 /*
1218 state utility object, used to track d3 states in the models
1219 */
1220 nv.utils.state = function(){
1221 if (!(this instanceof nv.utils.state)) {
1222 return new nv.utils.state();
1224 var state = {};
1225 var _self = this;
1226 var _setState = function(){};
1227 var _getState = function(){ return {}; };
1228 var init = null;
1229 var changed = null;
1231 this.dispatch = d3.dispatch('change', 'set');
1233 this.dispatch.on('set', function(state){
1234 _setState(state, true);
1235 });
1237 this.getter = function(fn){
1238 _getState = fn;
1239 return this;
1240 };
1242 this.setter = function(fn, callback) {
1243 if (!callback) {
1244 callback = function(){};
1246 _setState = function(state, update){
1247 fn(state);
1248 if (update) {
1249 callback();
1251 };
1252 return this;
1253 };
1255 this.init = function(state){
1256 init = init || {};
1257 nv.utils.deepExtend(init, state);
1258 };
1260 var _set = function(){
1261 var settings = _getState();
1263 if (JSON.stringify(settings) === JSON.stringify(state)) {
1264 return false;
1267 for (var key in settings) {
1268 if (state[key] === undefined) {
1269 state[key] = {};
1271 state[key] = settings[key];
1272 changed = true;
1274 return true;
1275 };
1277 this.update = function(){
1278 if (init) {
1279 _setState(init, false);
1280 init = null;
1282 if (_set.call(this)) {
1283 this.dispatch.change(state);
1285 };
1287 };
1290 /*
1291 Snippet of code you can insert into each nv.models.* to give you the ability to
1292 do things like:
1293 chart.options({
1294 showXAxis: true,
1295 tooltips: true
1296 });
1298 To enable in the chart:
1299 chart.options = nv.utils.optionsFunc.bind(chart);
1300 */
1301 nv.utils.optionsFunc = function(args) {
1302 if (args) {
1303 d3.map(args).forEach((function(key,value) {
1304 if (typeof this[key] === "function") {
1305 this[key](value);
1307 }).bind(this));
1309 return this;
1310 };
1313 /*
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
1319 */
1320 nv.utils.calcTicksX = function(numTicks, data) {
1321 // find max number of values from all data streams
1322 var numValues = 1;
1323 var i = 0;
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);
1337 return numTicks;
1338 };
1341 /*
1342 returns number of ticks to actually use on Y axis, based on chart data
1343 */
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);
1347 };
1350 /*
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.
1357 */
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];
1362 } else {
1363 chart[name] = function (_) {
1364 if (!arguments.length) return chart._options[name];
1365 chart._overrides[name] = true;
1366 chart._options[name] = _;
1367 return chart;
1368 };
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] = _;
1376 return chart;
1379 };
1382 /*
1383 Add all options in an options object to the chart
1384 */
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]);
1393 };
1396 /*
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
1400 */
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);
1406 };
1409 /*
1410 Remove duplicates from an array
1411 */
1412 nv.utils.arrayUnique = function(a) {
1413 return a.sort().filter(function(item, pos) {
1414 return !pos || item != a[pos - 1];
1415 });
1416 };
1419 /*
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){...});
1423 */
1424 nv.utils.symbolMap = d3.map();
1427 /*
1428 Replaces d3.svg.symbol so that we can look both there and our own map
1429 */
1430 nv.utils.symbol = function() {
1431 var type,
1432 size = 64;
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)();
1438 } else {
1439 return nv.utils.symbolMap.get(t)(s);
1442 symbol.type = function(_) {
1443 if (!arguments.length) return type;
1444 type = d3.functor(_);
1445 return symbol;
1446 };
1447 symbol.size = function(_) {
1448 if (!arguments.length) return size;
1449 size = d3.functor(_);
1450 return symbol;
1451 };
1452 return symbol;
1453 };
1456 /*
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
1461 */
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 || []));
1475 };
1478 /*
1479 Runs common initialize code on the svg before the chart builds
1480 */
1481 nv.utils.initSVG = function(svg) {
1482 svg.classed({'nvd3-svg':true});
1483 };
1486 /*
1487 Sanitize and provide default for the container height.
1488 */
1489 nv.utils.sanitizeHeight = function(height, container) {
1490 return (height || parseInt(container.style('height'), 10) || 400);
1491 };
1494 /*
1495 Sanitize and provide default for the container width.
1496 */
1497 nv.utils.sanitizeWidth = function(width, container) {
1498 return (width || parseInt(container.style('width'), 10) || 960);
1499 };
1502 /*
1503 Calculate the available height for a chart.
1504 */
1505 nv.utils.availableHeight = function(height, container, margin) {
1506 return nv.utils.sanitizeHeight(height, container) - margin.top - margin.bottom;
1507 };
1509 /*
1510 Calculate the available width for a chart.
1511 */
1512 nv.utils.availableWidth = function(width, container, margin) {
1513 return nv.utils.sanitizeWidth(width, container) - margin.left - margin.right;
1514 };
1516 /*
1517 Clear any rendered chart components and display a chart's 'noData' message
1518 */
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');
1539 noDataText
1540 .attr('x', x)
1541 .attr('y', y)
1542 .text(function(t){ return t; });
1543 };
1545 nv.models.axis = function() {
1546 "use strict";
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
1560 , rotateLabels = 0
1561 , rotateYLabel = true
1562 , staggerLabels = false
1563 , isOrdinal = false
1564 , ticks = null
1565 , axisLabelDistance = 0
1566 , duration = 250
1567 , dispatch = d3.dispatch('renderEnd')
1569 axis
1570 .scale(scale)
1571 .orient('bottom')
1572 .tickFormat(function(d) { return d })
1575 //============================================================
1576 // Private Variables
1577 //------------------------------------------------------------
1579 var scale0;
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');
1594 if (ticks !== null)
1595 axis.ticks(ticks);
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();
1605 if (fmt == null) {
1606 fmt = scale0.tickFormat();
1609 var axisLabel = g.selectAll('text.nv-axislabel')
1610 .data([axisLabelText || null]);
1611 axisLabel.exit().remove();
1613 var xLabelMargin;
1614 var axisMaxMin;
1615 var w;
1616 switch (axis.orient()) {
1617 case 'top':
1618 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1619 if (scale.range().length < 2) {
1620 w = 0;
1621 } else if (scale.range().length === 2) {
1622 w = scale.range()[1];
1623 } else {
1624 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1626 axisLabel
1627 .attr('text-anchor', 'middle')
1628 .attr('y', 0)
1629 .attr('x', w/2);
1630 if (showMaxMin) {
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(' ')
1635 }).append('text');
1636 axisMaxMin.exit().remove();
1637 axisMaxMin
1638 .attr('transform', function(d,i) {
1639 return 'translate(' + nv.utils.NaNtoZero(scale(d)) + ',0)'
1640 })
1641 .select('text')
1642 .attr('dy', '-0.5em')
1643 .attr('y', -axis.tickPadding())
1644 .attr('text-anchor', 'middle')
1645 .text(function(d,i) {
1646 var v = fmt(d);
1647 return ('' + v).match('NaN') ? '' : v;
1648 });
1649 axisMaxMin.watchTransition(renderWatch, 'min-max top')
1650 .attr('transform', function(d,i) {
1651 return 'translate(' + nv.utils.NaNtoZero(scale.range()[i]) + ',0)'
1652 });
1654 break;
1655 case 'bottom':
1656 xLabelMargin = axisLabelDistance + 36;
1657 var maxTextWidth = 30;
1658 var textHeight = 0;
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;
1668 });
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;
1673 //Rotate all xTicks
1674 xTicks
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) {
1680 w = 0;
1681 } else if (scale.range().length === 2) {
1682 w = scale.range()[1];
1683 } else {
1684 w = scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]);
1686 axisLabel
1687 .attr('text-anchor', 'middle')
1688 .attr('y', xLabelMargin)
1689 .attr('x', w/2);
1690 if (showMaxMin) {
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(' ')
1697 }).append('text');
1698 axisMaxMin.exit().remove();
1699 axisMaxMin
1700 .attr('transform', function(d,i) {
1701 return 'translate(' + nv.utils.NaNtoZero((scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0))) + ',0)'
1702 })
1703 .select('text')
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) {
1709 var v = fmt(d);
1710 return ('' + v).match('NaN') ? '' : v;
1711 });
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)'
1715 });
1717 if (staggerLabels)
1718 xTicks
1719 .attr('transform', function(d,i) {
1720 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
1721 });
1723 break;
1724 case 'right':
1725 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1726 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());
1731 if (showMaxMin) {
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(' ')
1736 }).append('text')
1737 .style('opacity', 0);
1738 axisMaxMin.exit().remove();
1739 axisMaxMin
1740 .attr('transform', function(d,i) {
1741 return 'translate(0,' + nv.utils.NaNtoZero(scale(d)) + ')'
1742 })
1743 .select('text')
1744 .attr('dy', '.32em')
1745 .attr('y', 0)
1746 .attr('x', axis.tickPadding())
1747 .style('text-anchor', 'start')
1748 .text(function(d, i) {
1749 var v = fmt(d);
1750 return ('' + v).match('NaN') ? '' : v;
1751 });
1752 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1753 .attr('transform', function(d,i) {
1754 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1755 })
1756 .select('text')
1757 .style('opacity', 1);
1759 break;
1760 case 'left':
1761 /*
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;
1767 });
1768 */
1769 axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1770 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());
1775 if (showMaxMin) {
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(' ')
1780 }).append('text')
1781 .style('opacity', 0);
1782 axisMaxMin.exit().remove();
1783 axisMaxMin
1784 .attr('transform', function(d,i) {
1785 return 'translate(0,' + nv.utils.NaNtoZero(scale0(d)) + ')'
1786 })
1787 .select('text')
1788 .attr('dy', '.32em')
1789 .attr('y', 0)
1790 .attr('x', -axis.tickPadding())
1791 .attr('text-anchor', 'end')
1792 .text(function(d,i) {
1793 var v = fmt(d);
1794 return ('' + v).match('NaN') ? '' : v;
1795 });
1796 axisMaxMin.watchTransition(renderWatch, 'min-max right')
1797 .attr('transform', function(d,i) {
1798 return 'translate(0,' + nv.utils.NaNtoZero(scale.range()[i]) + ')'
1799 })
1800 .select('text')
1801 .style('opacity', 1);
1803 break;
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!!
1818 });
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) {
1823 return !i ? 1 : 0
1824 });
1828 if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1829 var maxMinRange = [];
1830 wrap.selectAll('g.nv-axisMaxMin')
1831 .each(function(d,i) {
1832 try {
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)
1837 }catch (err) {
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);
1843 });
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();
1849 else
1850 d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1852 });
1855 //Highlight zero tick line
1856 g.selectAll('.tick')
1857 .filter(function (d) {
1858 /*
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.
1862 */
1863 return !parseFloat(Math.round(d * 100000) / 1000000) && (d !== undefined)
1864 })
1865 .classed('zero', true);
1867 //store old scales for use in transitions on update
1868 scale0 = scale.copy();
1870 });
1872 renderWatch.renderEnd('axis immediate');
1873 return chart;
1876 //============================================================
1877 // Expose Public Variables
1878 //------------------------------------------------------------
1880 // expose chart's sub-components
1881 chart.axis = axis;
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;
1903 }},
1904 duration: {get: function(){return duration;}, set: function(_){
1905 duration=_;
1906 renderWatch.reset(duration);
1907 }},
1908 scale: {get: function(){return scale;}, set: function(_){
1909 scale = _;
1910 axis.scale(scale);
1911 isOrdinal = typeof scale.rangeBands === 'function';
1912 nv.utils.inheritOptionsD3(chart, scale, ['domain', 'range', 'rangeBand', 'rangeBands']);
1913 }}
1914 });
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']);
1920 return chart;
1921 };
1922 nv.models.boxPlot = function() {
1923 "use strict";
1925 //============================================================
1926 // Public Variables with Default Settings
1927 //------------------------------------------------------------
1929 var margin = {top: 0, right: 0, bottom: 0, left: 0}
1930 , width = 960
1931 , height = 500
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()
1938 , container = null
1939 , xDomain
1940 , yDomain
1941 , xRange
1942 , yRange
1943 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
1944 , duration = 250
1945 , maxBoxWidth = null
1948 //============================================================
1949 // Private Variables
1950 //------------------------------------------------------------
1952 var x0, y0;
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);
1964 // Setup Scales
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
1969 var yData = []
1970 if (!yDomain) {
1971 // (y-range is based on quartiles, whiskers and outliers)
1973 // lower values
1974 var yMin = d3.min(data.map(function(d) {
1975 var min_arr = [];
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);
1982 }));
1984 // upper values
1985 var yMax = d3.max(data.map(function(d) {
1986 var max_arr = [];
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);
1993 }));
1995 yData = [ yMin, yMax ] ;
1998 y.domain(yDomain || yData);
1999 y.range(yRange || [availableHeight, 0]);
2001 //store old scales if they exist
2002 x0 = x0 || x;
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);
2012 boxplots
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 });
2016 boxplots
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)';
2023 });
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) {
2034 box.append('line')
2035 .style('stroke', (d.color) ? d.color : color(d,i))
2036 .attr('class', 'nv-boxplot-whisker nv-boxplot-' + key);
2038 box.append('line')
2039 .style('stroke', (d.color) ? d.color : color(d,i))
2040 .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
2042 });
2043 });
2045 // outliers
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; }
2049 else { return []; }
2050 });
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) },
2057 e: d3.event
2058 });
2059 })
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) },
2064 e: d3.event
2065 });
2066 })
2067 .on('mousemove', function(d,i) {
2068 dispatch.elementMousemove({e: d3.event});
2069 });
2071 outliers.attr('class', 'nv-boxplot-outlier');
2072 outliers
2073 .watchTransition(renderWatch, 'nv-boxplot: nv-boxplot-outlier')
2074 .attr('cx', x.rangeBand() * .45)
2075 .attr('cy', function(d,i,j) { return y(d); })
2076 .attr('r', '3');
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]); });
2100 });
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) },
2108 e: d3.event
2109 });
2110 })
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) },
2115 e: d3.event
2116 });
2117 })
2118 .on('mousemove', function(d,i) {
2119 dispatch.elementMousemove({e: d3.event});
2120 });
2121 });
2123 // boxes
2124 boxEnter.append('rect')
2125 .attr('class', 'nv-boxplot-box')
2126 // tooltip events
2127 .on('mouseover', function(d,i) {
2128 d3.select(this).classed('hover', true);
2129 dispatch.elementMouseover({
2130 key: d.label,
2131 value: d.label,
2132 series: [
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) }
2136 ],
2137 data: d,
2138 index: i,
2139 e: d3.event
2140 });
2141 })
2142 .on('mouseout', function(d,i) {
2143 d3.select(this).classed('hover', false);
2144 dispatch.elementMouseout({
2145 key: d.label,
2146 value: d.label,
2147 series: [
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) }
2151 ],
2152 data: d,
2153 index: i,
2154 e: d3.event
2155 });
2156 })
2157 .on('mousemove', function(d,i) {
2158 dispatch.elementMousemove({e: d3.event});
2159 });
2161 // box transitions
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) });
2172 // median line
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
2183 x0 = x.copy();
2184 y0 = y.copy();
2185 });
2187 renderWatch.renderEnd('nv-boxplot immediate');
2188 return chart;
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;
2220 }},
2221 color: {get: function(){return color;}, set: function(_){
2222 color = nv.utils.getColor(_);
2223 }},
2224 duration: {get: function(){return duration;}, set: function(_){
2225 duration = _;
2226 renderWatch.reset(duration);
2227 }}
2228 });
2230 nv.utils.initOptions(chart);
2232 return chart;
2233 };
2234 nv.models.boxPlotChart = function() {
2235 "use strict";
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}
2247 , width = null
2248 , height = null
2249 , color = nv.utils.getColor()
2250 , showXAxis = true
2251 , showYAxis = true
2252 , rightAlignYAxis = false
2253 , staggerLabels = false
2254 , tooltip = nv.models.tooltip()
2255 , x
2256 , y
2257 , noData = "No Data Available."
2258 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate', 'renderEnd')
2259 , duration = 250
2262 xAxis
2263 .orient('bottom')
2264 .showMaxMin(false)
2265 .tickFormat(function(d) { return d })
2267 yAxis
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),
2288 that = 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);
2298 };
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');
2311 noDataText
2312 .attr('x', margin.left + availableWidth / 2)
2313 .attr('y', margin.top + availableHeight / 2)
2314 .text(function(d) { return d });
2316 return chart;
2317 } else {
2318 container.selectAll('.nv-noData').remove();
2321 // Setup Scales
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')
2334 .append('line');
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)
2346 boxplot
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())
2358 .append('rect');
2360 g.select('#nv-x-label-clip-' + boxplot.id() + ' rect')
2361 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
2362 .attr('height', 16)
2363 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
2365 // Setup Axes
2366 if (showXAxis) {
2367 xAxis
2368 .scale(x)
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) {
2377 xTicks
2378 .selectAll('text')
2379 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
2383 if (showYAxis) {
2384 yAxis
2385 .scale(y)
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);
2392 // Zero line
2393 g.select(".nv-zeroLine line")
2394 .attr("x1",0)
2395 .attr("x2",availableWidth)
2396 .attr("y1", y(0))
2397 .attr("y2", y(0))
2400 //============================================================
2401 // Event Handling/Dispatching (in chart's scope)
2402 //------------------------------------------------------------
2403 });
2405 renderWatch.renderEnd('nv-boxplot chart immediate');
2406 return chart;
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);
2415 });
2417 boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
2418 tooltip.data(evt).hidden(true);
2419 });
2421 boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
2422 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2423 });
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;
2454 }},
2455 duration: {get: function(){return duration;}, set: function(_){
2456 duration = _;
2457 renderWatch.reset(duration);
2458 boxplot.duration(duration);
2459 xAxis.duration(duration);
2460 yAxis.duration(duration);
2461 }},
2462 color: {get: function(){return color;}, set: function(_){
2463 color = nv.utils.getColor(_);
2464 boxplot.color(color);
2465 }},
2466 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
2467 rightAlignYAxis = _;
2468 yAxis.orient( (_) ? 'right' : 'left');
2469 }}
2470 });
2472 nv.utils.inheritOptions(chart, boxplot);
2473 nv.utils.initOptions(chart);
2475 return 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() {
2482 "use strict";
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
2490 , reverse = false
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.)
2498 , width = 380
2499 , height = 30
2500 , container = null
2501 , tickFormat = null
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();
2521 // Setup Scales
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])
2530 .range(x1.range());
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))
2567 .datum(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({
2587 value: measurez[0],
2588 label: measureLabelz[0] || 'Current',
2589 color: d3.select(this).style("fill")
2590 })
2591 })
2592 .on('mousemove', function() {
2593 dispatch.elementMousemove({
2594 value: measurez[0],
2595 label: measureLabelz[0] || 'Current',
2596 color: d3.select(this).style("fill")
2597 })
2598 })
2599 .on('mouseout', function() {
2600 dispatch.elementMouseout({
2601 value: measurez[0],
2602 label: measureLabelz[0] || 'Current',
2603 color: d3.select(this).style("fill")
2604 })
2605 });
2607 var h3 = availableHeight / 6;
2609 var markerData = markerz.map( function(marker, index) {
2610 return {value: marker, label: markerLabelz[index]}
2611 });
2612 gEnter
2613 .selectAll("path.nv-markerTriangle")
2614 .data(markerData)
2615 .enter()
2616 .append('path')
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({
2622 value: d.value,
2623 label: d.label || 'Previous',
2624 color: d3.select(this).style("fill"),
2625 pos: [x1(d.value), availableHeight/2]
2626 })
2628 })
2629 .on('mousemove', function(d) {
2630 dispatch.elementMousemove({
2631 value: d.value,
2632 label: d.label || 'Previous',
2633 color: d3.select(this).style("fill")
2634 })
2635 })
2636 .on('mouseout', function(d, i) {
2637 dispatch.elementMouseout({
2638 value: d.value,
2639 label: d.label || 'Previous',
2640 color: d3.select(this).style("fill")
2641 })
2642 });
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({
2648 value: d,
2649 label: label,
2650 color: d3.select(this).style("fill")
2651 })
2652 })
2653 .on('mousemove', function() {
2654 dispatch.elementMousemove({
2655 value: measurez[0],
2656 label: measureLabelz[0] || 'Previous',
2657 color: d3.select(this).style("fill")
2658 })
2659 })
2660 .on('mouseout', function(d,i) {
2661 var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
2662 dispatch.elementMouseout({
2663 value: d,
2664 label: label,
2665 color: d3.select(this).style("fill")
2666 })
2667 });
2668 });
2670 return chart;
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;
2696 }},
2697 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2698 orient = _;
2699 reverse = orient == 'right' || orient == 'bottom';
2700 }},
2701 color: {get: function(){return color;}, set: function(_){
2702 color = nv.utils.getColor(_);
2703 }}
2704 });
2706 nv.utils.initOptions(chart);
2707 return chart;
2708 };
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() {
2716 "use strict";
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
2726 , reverse = false
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 }
2731 , width = null
2732 , height = 55
2733 , tickFormat = null
2734 , ticks = null
2735 , noData = null
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,
2748 that = this;
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)
2756 return chart;
2757 } else {
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])
2784 .range(x1.range());
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')
2801 .attr('dy', '1em')
2802 .text(function(d) { return d.subtitle; });
2804 bullet
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);
2818 });
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')
2832 .attr('dy', '1em')
2833 .attr('y', availableHeight * 7 / 6)
2834 .text(format);
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)
2852 .remove();
2853 });
2855 d3.timer.flush();
2856 return chart;
2859 //============================================================
2860 // Event Handling/Dispatching (out of chart's scope)
2861 //------------------------------------------------------------
2863 bullet.dispatch.on('elementMouseover.tooltip', function(evt) {
2864 evt['series'] = {
2865 key: evt.label,
2866 value: evt.value,
2867 color: evt.color
2868 };
2869 tooltip.data(evt).hidden(false);
2870 });
2872 bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
2873 tooltip.hidden(true);
2874 });
2876 bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
2877 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2878 });
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(!!_);
2906 }},
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(_);
2911 }},
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;
2919 }},
2920 orient: {get: function(){return orient;}, set: function(_){ // left, right, top, bottom
2921 orient = _;
2922 reverse = orient == 'right' || orient == 'bottom';
2923 }}
2924 });
2926 nv.utils.inheritOptions(chart, bullet);
2927 nv.utils.initOptions(chart);
2929 return chart;
2930 };
2934 nv.models.candlestickBar = function() {
2935 "use strict";
2937 //============================================================
2938 // Public Variables with Default Settings
2939 //------------------------------------------------------------
2941 var margin = {top: 0, right: 0, bottom: 0, left: 0}
2942 , width = null
2943 , height = null
2944 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2945 , container
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 }
2954 , forceX = []
2955 , forceY = []
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
2957 , clipEdge = true
2958 , color = nv.utils.defaultColor()
2959 , interactive = false
2960 , xDomain
2961 , yDomain
2962 , xRange
2963 , yRange
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;
2982 // Setup Scales
2983 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
2985 if (padData)
2986 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
2987 else
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])
2998 x.domain()[0] ?
2999 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3000 : x.domain([-1,1]);
3002 if (y.domain()[0] === y.domain()[1])
3003 y.domain()[0] ?
3004 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3005 : y.domain([-1,1]);
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 + ')');
3018 container
3019 .on('click', function(d,i) {
3020 dispatch.chartClick({
3021 data: d,
3022 index: i,
3023 pos: d3.event,
3024 id: id
3025 });
3026 });
3028 defsEnter.append('clipPath')
3029 .attr('id', 'nv-chart-clip-path-' + id)
3030 .append('rect');
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)'; })
3049 .attr('x1', 0)
3050 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
3051 .attr('x2', 0)
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))
3059 + ')';
3060 })
3061 .attr('x', 0)
3062 .attr('y', 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);
3068 });
3070 container.selectAll('.nv-candlestick-lines').transition()
3071 .attr('transform', function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)'; })
3072 .attr('x1', 0)
3073 .attr('y1', function(d, i) { return y(getHigh(d, i)); })
3074 .attr('x2', 0)
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))
3081 + ')';
3082 })
3083 .attr('x', 0)
3084 .attr('y', 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);
3090 });
3091 });
3093 return chart;
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)
3103 };
3105 chart.clearHighlights = function() {
3106 container.select(".nv-candlestickBar .nv-tick.hover")
3107 .classed("hover", false)
3109 };
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;
3148 }},
3149 color: {get: function(){return color;}, set: function(_){
3150 color = nv.utils.getColor(_);
3151 }}
3152 });
3154 nv.utils.initOptions(chart);
3155 return chart;
3156 };
3158 nv.models.cumulativeLineChart = function() {
3159 "use strict";
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()
3176 , width = null
3177 , height = null
3178 , showLegend = true
3179 , showXAxis = true
3180 , showYAxis = true
3181 , rightAlignYAxis = false
3182 , showControls = true
3183 , useInteractiveGuideline = false
3184 , rescaleY = true
3185 , x //can be accessed via chart.xScale()
3186 , y //can be accessed via chart.yScale()
3187 , id = lines.id()
3188 , state = nv.utils.state()
3189 , defaultState = null
3190 , noData = null
3191 , average = function(d) { return d.average }
3192 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
3193 , transitionDuration = 250
3194 , duration = 250
3195 , noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
3198 state.index = 0;
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);
3208 });
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) {
3222 return function(){
3223 return {
3224 active: data.map(function(d) { return !d.disabled }),
3225 index: index.i,
3226 rescaleY: rescaleY
3227 };
3229 };
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];
3240 });
3242 };
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);
3253 var that = this;
3255 var availableWidth = nv.utils.availableWidth(width, container, margin),
3256 availableHeight = nv.utils.availableHeight(height, container, margin);
3258 chart.update = function() {
3259 if (duration === 0)
3260 container.call(chart);
3261 else
3262 container.transition().duration(duration).call(chart)
3263 };
3264 chart.container = this;
3266 state
3267 .setter(stateSetter(data), chart.update)
3268 .getter(stateGetter(data))
3269 .update();
3271 // DEPRECATED set state.disableddisabled
3272 state.disabled = data.map(function(d) { return !!d.disabled });
3274 if (!defaultState) {
3275 var key;
3276 defaultState = {};
3277 for (key in state) {
3278 if (state[key] instanceof Array)
3279 defaultState[key] = state[key].slice(0);
3280 else
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));
3299 updateZero();
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)
3314 return chart;
3315 } else {
3316 container.selectAll('.nv-noData').remove();
3319 // Setup Scales
3320 x = lines.xScale();
3321 y = lines.yScale();
3323 if (!rescaleY) {
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;
3332 return [
3333 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
3334 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
3335 ];
3336 });
3338 var completeDomain = [
3339 d3.min(seriesDomains, function(d) { return d[0] }),
3340 d3.max(seriesDomains, function(d) { return d[1] })
3341 ];
3343 lines.yDomain(completeDomain);
3344 } else {
3345 lines.yDomain(null);
3348 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
3349 .range([0, availableWidth])
3350 .clamp(true);
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');
3369 // Legend
3370 if (showLegend) {
3371 legend.width(availableWidth);
3373 g.select('.nv-legendWrap')
3374 .datum(data)
3375 .call(legend);
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) +')')
3386 // Controls
3387 if (showControls) {
3388 var controlsData = [
3389 { key: 'Re-scale y-axis', disabled: !rescaleY }
3390 ];
3392 controls
3393 .width(140)
3394 .color(['#444', '#444', '#444'])
3395 .rightAlign(false)
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) +')')
3402 .call(controls);
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) {
3426 interactiveLayer
3427 .width(availableWidth)
3428 .height(availableHeight)
3429 .margin({left:margin.left,top:margin.top})
3430 .svgContainer(container)
3431 .xScale(x);
3432 wrap.select(".nv-interactive").call(interactiveLayer);
3435 gEnter.select('.nv-background')
3436 .append('rect');
3438 g.select('.nv-background rect')
3439 .attr('width', availableWidth)
3440 .attr('height', availableHeight);
3442 lines
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) {
3458 d.seriesIndex = i;
3459 });
3461 var avgLineData = data.filter(function(d) {
3462 return !d.disabled && !!average(d);
3463 });
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;
3473 return yVal;
3474 };
3476 avgLines.enter()
3477 .append('line')
3478 .style('stroke-width',2)
3479 .style('stroke-dasharray','10,10')
3480 .style('stroke',function (d,i) {
3481 return lines.color()(d,d.seriesIndex);
3482 })
3483 .attr('x1',0)
3484 .attr('x2',availableWidth)
3485 .attr('y1', getAvgLineY)
3486 .attr('y2', getAvgLineY);
3488 avgLines
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;
3493 return 1;
3494 })
3495 .attr('x1',0)
3496 .attr('x2',availableWidth)
3497 .attr('y1', getAvgLineY)
3498 .attr('y2', getAvgLineY);
3500 avgLines.exit().remove();
3502 //Create index line
3503 var indexLine = linesWrap.selectAll('.nv-indexLine')
3504 .data([index]);
3505 indexLine.enter().append('rect').attr('class', 'nv-indexLine')
3506 .attr('width', 3)
3507 .attr('x', -2)
3508 .attr('fill', 'red')
3509 .attr('fill-opacity', .5)
3510 .style("pointer-events","all")
3511 .call(indexDrag);
3513 indexLine
3514 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
3515 .attr('height', availableHeight);
3517 // Setup Axes
3518 if (showXAxis) {
3519 xAxis
3520 .scale(x)
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')
3527 .call(xAxis);
3530 if (showYAxis) {
3531 yAxis
3532 .scale(y)
3533 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3534 .tickSize( -availableWidth, 0);
3536 g.select('.nv-y.nv-axis')
3537 .call(yAxis);
3540 //============================================================
3541 // Event Handling/Dispatching (in chart's scope)
3542 //------------------------------------------------------------
3544 function updateZero() {
3545 indexLine
3546 .data([index]);
3548 //When dragging the index line, turn off line transitions.
3549 // Then turn them back on when done dragging.
3550 var oldDuration = chart.duration();
3551 chart.duration(0);
3552 chart.update();
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);
3565 updateZero();
3566 });
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);
3576 updateZero();
3577 });
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);
3585 chart.update();
3586 });
3588 legend.dispatch.on('stateChange', function(newState) {
3589 for (var key in newState)
3590 state[key] = newState[key];
3591 dispatch.stateChange(state);
3592 chart.update();
3593 });
3595 interactiveLayer.dispatch.on('elementMousemove', function(e) {
3596 lines.clearHighlights();
3597 var singlePoint, pointIndex, pointXLocation, allData = [];
3599 data
3600 .filter(function(series, i) {
3601 series.seriesIndex = i;
3602 return !series.disabled;
3603 })
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));
3611 allData.push({
3612 key: series.key,
3613 value: chart.y()(point, pointIndex),
3614 color: color(series,series.seriesIndex)
3615 });
3616 });
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);
3634 })
3635 .data(
3637 value: xValue,
3638 series: allData
3640 )();
3642 interactiveLayer.renderGuideLine(pointXLocation);
3643 });
3645 interactiveLayer.dispatch.on("elementMouseout",function(e) {
3646 lines.clearHighlights();
3647 });
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];
3654 });
3656 state.disabled = e.disabled;
3659 if (typeof e.index !== 'undefined') {
3660 index.i = e.index;
3661 index.x = dx(index.i);
3663 state.index = e.index;
3665 indexLine
3666 .data([index]);
3669 if (typeof e.rescaleY !== 'undefined') {
3670 rescaleY = e.rescaleY;
3673 chart.update();
3674 });
3676 });
3678 renderWatch.renderEnd('cumulativeLineChart immediate');
3680 return chart;
3683 //============================================================
3684 // Event Handling/Dispatching (out of chart's scope)
3685 //------------------------------------------------------------
3687 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
3688 var point = {
3689 x: chart.x()(evt.point),
3690 y: chart.y()(evt.point),
3691 color: evt.point.color
3692 };
3693 evt.point = point;
3694 tooltip.data(evt).position(evt.pos).hidden(false);
3695 });
3697 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
3698 tooltip.hidden(true)
3699 });
3701 //============================================================
3702 // Functions
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) {
3710 if (!line.values) {
3711 return line;
3713 var indexValue = line.values[idx];
3714 if (indexValue == null) {
3715 return line;
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;
3724 return line;
3727 line.tempDisabled = false;
3729 line.values = line.values.map(function(point, pointIndex) {
3730 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
3731 return point;
3732 });
3734 return line;
3735 })
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(!!_);
3774 }},
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(_);
3779 }},
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;
3787 }},
3788 color: {get: function(){return color;}, set: function(_){
3789 color = nv.utils.getColor(_);
3790 legend.color(color);
3791 }},
3792 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
3793 useInteractiveGuideline = _;
3794 if (_ === true) {
3795 chart.interactive(false);
3796 chart.useVoronoi(false);
3798 }},
3799 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
3800 rightAlignYAxis = _;
3801 yAxis.orient( (_) ? 'right' : 'left');
3802 }},
3803 duration: {get: function(){return duration;}, set: function(_){
3804 duration = _;
3805 lines.duration(duration);
3806 xAxis.duration(duration);
3807 yAxis.duration(duration);
3808 renderWatch.reset(duration);
3809 }}
3810 });
3812 nv.utils.inheritOptions(chart, lines);
3813 nv.utils.initOptions(chart);
3815 return chart;
3816 };
3817 //TODO: consider deprecating by adding necessary features to multiBar model
3818 nv.models.discreteBar = function() {
3819 "use strict";
3821 //============================================================
3822 // Public Variables with Default Settings
3823 //------------------------------------------------------------
3825 var margin = {top: 0, right: 0, bottom: 0, left: 0}
3826 , width = 960
3827 , height = 500
3828 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3829 , container
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')
3838 , xDomain
3839 , yDomain
3840 , xRange
3841 , yRange
3842 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
3843 , rectClass = 'discreteBar'
3844 , duration = 250
3847 //============================================================
3848 // Private Variables
3849 //------------------------------------------------------------
3851 var x0, y0;
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) {
3866 point.series = i;
3867 });
3868 });
3870 // Setup Scales
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 }
3876 })
3877 });
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
3888 x0 = x0 || x;
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);
3906 groups.exit()
3907 .watchTransition(renderWatch, 'discreteBar: exit groups')
3908 .style('stroke-opacity', 1e-6)
3909 .style('fill-opacity', 1e-6)
3910 .remove();
3911 groups
3912 .attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3913 .classed('hover', function(d) { return d.hover });
3914 groups
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) + ')'
3926 })
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({
3930 data: d,
3931 index: i,
3932 color: d3.select(this).style("fill")
3933 });
3934 })
3935 .on('mouseout', function(d,i) {
3936 d3.select(this).classed('hover', false);
3937 dispatch.elementMouseout({
3938 data: d,
3939 index: i,
3940 color: d3.select(this).style("fill")
3941 });
3942 })
3943 .on('mousemove', function(d,i) {
3944 dispatch.elementMousemove({
3945 data: d,
3946 index: i,
3947 color: d3.select(this).style("fill")
3948 });
3949 })
3950 .on('click', function(d,i) {
3951 dispatch.elementClick({
3952 data: d,
3953 index: i,
3954 color: d3.select(this).style("fill")
3955 });
3956 d3.event.stopPropagation();
3957 })
3958 .on('dblclick', function(d,i) {
3959 dispatch.elementDblClick({
3960 data: d,
3961 index: i,
3962 color: d3.select(this).style("fill")
3963 });
3964 d3.event.stopPropagation();
3965 });
3967 barsEnter.append('rect')
3968 .attr('height', 0)
3969 .attr('width', x.rangeBand() * .9 / data.length )
3971 if (showValues) {
3972 barsEnter.append('text')
3973 .attr('text-anchor', 'middle')
3976 bars.select('text')
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 })
3983 } else {
3984 bars.selectAll('text').remove();
3987 bars
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) })
3991 .select('rect')
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 ?
4000 y(0) :
4001 y(0) - y(getY(d,i)) < 1 ?
4002 y(0) - 1 : //make 1 px positive bars show up above y=0
4003 y(getY(d,i));
4005 return 'translate(' + left + ', ' + top + ')'
4006 })
4007 .select('rect')
4008 .attr('height', function(d,i) {
4009 return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
4010 });
4013 //store old scales for use in transitions on update
4014 x0 = x.copy();
4015 y0 = y.copy();
4017 });
4019 renderWatch.renderEnd('discreteBar immediate');
4020 return chart;
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;
4054 }},
4055 color: {get: function(){return color;}, set: function(_){
4056 color = nv.utils.getColor(_);
4057 }},
4058 duration: {get: function(){return duration;}, set: function(_){
4059 duration = _;
4060 renderWatch.reset(duration);
4061 }}
4062 });
4064 nv.utils.initOptions(chart);
4066 return chart;
4067 };
4069 nv.models.discreteBarChart = function() {
4070 "use strict";
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}
4083 , width = null
4084 , height = null
4085 , color = nv.utils.getColor()
4086 , showXAxis = true
4087 , showYAxis = true
4088 , rightAlignYAxis = false
4089 , staggerLabels = false
4090 , x
4091 , y
4092 , noData = null
4093 , dispatch = d3.dispatch('beforeUpdate','renderEnd')
4094 , duration = 250
4097 xAxis
4098 .orient('bottom')
4099 .showMaxMin(false)
4100 .tickFormat(function(d) { return d })
4102 yAxis
4103 .orient((rightAlignYAxis) ? 'right' : 'left')
4104 .tickFormat(d3.format(',.1f'))
4107 tooltip
4108 .duration(0)
4109 .headerEnabled(false)
4110 .valueFormatter(function(d, i) {
4111 return yAxis.tickFormat()(d, i);
4112 })
4113 .keyFormatter(function(d, i) {
4114 return xAxis.tickFormat()(d, i);
4115 });
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),
4131 that = 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);
4139 };
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);
4145 return chart;
4146 } else {
4147 container.selectAll('.nv-noData').remove();
4150 // Setup Scales
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')
4163 .append('line');
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)
4175 discretebar
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())
4187 .append('rect');
4189 g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
4190 .attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
4191 .attr('height', 16)
4192 .attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
4194 // Setup Axes
4195 if (showXAxis) {
4196 xAxis
4197 .scale(x)
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) {
4207 xTicks
4208 .selectAll('text')
4209 .attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
4213 if (showYAxis) {
4214 yAxis
4215 .scale(y)
4216 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4217 .tickSize( -availableWidth, 0);
4219 g.select('.nv-y.nv-axis').call(yAxis);
4222 // Zero line
4223 g.select(".nv-zeroLine line")
4224 .attr("x1",0)
4225 .attr("x2",availableWidth)
4226 .attr("y1", y(0))
4227 .attr("y2", y(0))
4229 });
4231 renderWatch.renderEnd('discreteBar chart immediate');
4232 return chart;
4235 //============================================================
4236 // Event Handling/Dispatching (out of chart's scope)
4237 //------------------------------------------------------------
4239 discretebar.dispatch.on('elementMouseover.tooltip', function(evt) {
4240 evt['series'] = {
4241 key: chart.x()(evt.data),
4242 value: chart.y()(evt.data),
4243 color: evt.color
4244 };
4245 tooltip.data(evt).hidden(false);
4246 });
4248 discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
4249 tooltip.hidden(true);
4250 });
4252 discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
4253 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
4254 });
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(!!_);
4282 }},
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(_);
4287 }},
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;
4295 }},
4296 duration: {get: function(){return duration;}, set: function(_){
4297 duration = _;
4298 renderWatch.reset(duration);
4299 discretebar.duration(duration);
4300 xAxis.duration(duration);
4301 yAxis.duration(duration);
4302 }},
4303 color: {get: function(){return color;}, set: function(_){
4304 color = nv.utils.getColor(_);
4305 discretebar.color(color);
4306 }},
4307 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
4308 rightAlignYAxis = _;
4309 yAxis.orient( (_) ? 'right' : 'left');
4310 }}
4311 });
4313 nv.utils.inheritOptions(chart, discretebar);
4314 nv.utils.initOptions(chart);
4316 return chart;
4319 nv.models.distribution = function() {
4320 "use strict";
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....
4327 , size = 8
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()
4332 , domain
4333 , duration = 250
4334 , dispatch = d3.dispatch('renderEnd')
4337 //============================================================
4340 //============================================================
4341 // Private Variables
4342 //------------------------------------------------------------
4344 var scale0;
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 //------------------------------------------------------------
4359 // Setup Scales
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');
4383 distWrap
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')
4393 // .transition()
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)
4397 .remove();
4398 dist
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')
4403 // .transition()
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();
4410 });
4411 renderWatch.renderEnd('distribution immediate');
4412 return chart;
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;
4428 return chart;
4429 };
4431 chart.width = function(_) {
4432 if (!arguments.length) return width;
4433 width = _;
4434 return chart;
4435 };
4437 chart.axis = function(_) {
4438 if (!arguments.length) return axis;
4439 axis = _;
4440 return chart;
4441 };
4443 chart.size = function(_) {
4444 if (!arguments.length) return size;
4445 size = _;
4446 return chart;
4447 };
4449 chart.getData = function(_) {
4450 if (!arguments.length) return getData;
4451 getData = d3.functor(_);
4452 return chart;
4453 };
4455 chart.scale = function(_) {
4456 if (!arguments.length) return scale;
4457 scale = _;
4458 return chart;
4459 };
4461 chart.color = function(_) {
4462 if (!arguments.length) return color;
4463 color = nv.utils.getColor(_);
4464 return chart;
4465 };
4467 chart.duration = function(_) {
4468 if (!arguments.length) return duration;
4469 duration = _;
4470 renderWatch.reset(duration);
4471 return chart;
4472 };
4473 //============================================================
4476 return chart;
4478 nv.models.furiousLegend = function() {
4479 "use strict";
4481 //============================================================
4482 // Public Variables with Default Settings
4483 //------------------------------------------------------------
4485 var margin = {top: 5, right: 0, bottom: 5, left: 0}
4486 , width = 400
4487 , height = 20
4488 , getKey = function(d) { return d.key }
4489 , color = nv.utils.getColor()
4490 , align = true
4491 , padding = 28 //define how much space between legend items. - recommend 32 for furious version
4492 , rightAlign = true
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)
4495 , expanded = false
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')
4514 .data(function(d) {
4515 if(vers != 'furious') return d;
4517 return d.filter(function(n) {
4518 return expanded ? true : !n.disengaged;
4519 });
4520 });
4521 var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4523 var seriesShape;
4525 if(vers == 'classic') {
4526 seriesEnter.append('circle')
4527 .style('stroke-width', 2)
4528 .attr('class','nv-legend-symbol')
4529 .attr('r', 5);
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')
4536 .attr('rx', 3)
4537 .attr('ry', 3);
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));
4551 });
4554 seriesEnter.append('text')
4555 .attr('text-anchor', 'start')
4556 .attr('class','nv-legend-text')
4557 .attr('dy', '.32em')
4558 .attr('dx', '8');
4560 var seriesText = series.select('text.nv-legend-text');
4562 series
4563 .on('mouseover', function(d,i) {
4564 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
4565 })
4566 .on('mouseout', function(d,i) {
4567 dispatch.legendMouseout(d,i);
4568 })
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();
4573 if (updateState) {
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});
4579 d.disabled = false;
4581 else {
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') {
4590 if(expanded) {
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;
4603 });
4607 dispatch.stateChange({
4608 disabled: data.map(function(d) { return !!d.disabled }),
4609 disengaged: data.map(function(d) { return !!d.disengaged })
4610 });
4613 })
4614 .on('dblclick', function(d,i) {
4615 if(vers == 'furious' && expanded) return;
4616 dispatch.legendDblclick(d,i);
4617 if (updateState) {
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;
4625 });
4626 d.disabled = false;
4627 if(vers == 'furious') d.userDisabled = d.disabled;
4628 dispatch.stateChange({
4629 disabled: data.map(function(d) { return !!d.disabled })
4630 });
4632 });
4634 series.classed('nv-disabled', function(d) { return d.userDisabled });
4635 series.exit().remove();
4637 seriesText
4638 .attr('fill', setTextColor)
4639 .text(getKey);
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
4644 var versPadding;
4645 switch(vers) {
4646 case 'furious' :
4647 versPadding = 23;
4648 break;
4649 case 'classic' :
4650 versPadding = 20;
4653 if (align) {
4655 var seriesWidths = [];
4656 series.each(function(d,i) {
4657 var legendText = d3.select(this).select('text');
4658 var nodeTextLength;
4659 try {
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();
4664 catch(e) {
4665 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4668 seriesWidths.push(nodeTextLength + padding);
4669 });
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 ) {
4682 columnWidths = [];
4683 seriesPerRow--;
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) {
4691 return prev + cur;
4692 });
4695 var xPositions = [];
4696 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4697 xPositions[i] = curX;
4698 curX += columnWidths[i];
4701 series
4702 .attr('transform', function(d, i) {
4703 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
4704 });
4706 //position legend as far right as possible within the total width
4707 if (rightAlign) {
4708 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4710 else {
4711 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4714 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
4716 } else {
4718 var ypos = 5,
4719 newxpos = 5,
4720 maxwidth = 0,
4721 xpos;
4722 series
4723 .attr('transform', function(d, i) {
4724 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
4725 xpos = newxpos;
4727 if (width < margin.left + margin.right + xpos + length) {
4728 newxpos = xpos = 5;
4729 ypos += versPadding;
4732 newxpos += length;
4733 if (newxpos > maxwidth) maxwidth = newxpos;
4735 return 'translate(' + xpos + ',' + ypos + ')';
4736 });
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
4746 seriesShape
4747 .attr('width', function(d,i) {
4748 return seriesText[0][i].getComputedTextLength() + 27;
4749 })
4750 .attr('height', 18)
4751 .attr('y', -9)
4752 .attr('x', -15)
4755 seriesShape
4756 .style('fill', setBGColor)
4757 .style('stroke', function(d,i) { return d.color || color(d, i) });
4758 });
4760 function setTextColor(d,i) {
4761 if(vers != 'furious') return '#000';
4762 if(expanded) {
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);
4772 } else {
4773 return !!d.disabled ? '#fff' : color(d,i);
4777 return chart;
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;
4806 }},
4807 color: {get: function(){return color;}, set: function(_){
4808 color = nv.utils.getColor(_);
4809 }}
4810 });
4812 nv.utils.initOptions(chart);
4814 return chart;
4815 };
4816 //TODO: consider deprecating and using multibar with single series for this
4817 nv.models.historicalBar = function() {
4818 "use strict";
4820 //============================================================
4821 // Public Variables with Default Settings
4822 //------------------------------------------------------------
4824 var margin = {top: 0, right: 0, bottom: 0, left: 0}
4825 , width = null
4826 , height = null
4827 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
4828 , container = null
4829 , x = d3.scale.linear()
4830 , y = d3.scale.linear()
4831 , getX = function(d) { return d.x }
4832 , getY = function(d) { return d.y }
4833 , forceX = []
4834 , forceY = [0]
4835 , padData = false
4836 , clipEdge = true
4837 , color = nv.utils.defaultColor()
4838 , xDomain
4839 , yDomain
4840 , xRange
4841 , yRange
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);
4858 // Setup Scales
4859 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
4861 if (padData)
4862 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
4863 else
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])
4871 x.domain()[0] ?
4872 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
4873 : x.domain([-1,1]);
4875 if (y.domain()[0] === y.domain()[1])
4876 y.domain()[0] ?
4877 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
4878 : y.domain([-1,1]);
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 + ')');
4890 container
4891 .on('click', function(d,i) {
4892 dispatch.chartClick({
4893 data: d,
4894 index: i,
4895 pos: d3.event,
4896 id: id
4897 });
4898 });
4900 defsEnter.append('clipPath')
4901 .attr('id', 'nv-chart-clip-path-' + id)
4902 .append('rect');
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')
4915 .attr('x', 0 )
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({
4923 data: d,
4924 index: i,
4925 color: d3.select(this).style("fill")
4926 });
4928 })
4929 .on('mouseout', function(d,i) {
4930 if (!interactive) return;
4931 d3.select(this).classed('hover', false);
4932 dispatch.elementMouseout({
4933 data: d,
4934 index: i,
4935 color: d3.select(this).style("fill")
4936 });
4937 })
4938 .on('mousemove', function(d,i) {
4939 if (!interactive) return;
4940 dispatch.elementMousemove({
4941 data: d,
4942 index: i,
4943 color: d3.select(this).style("fill")
4944 });
4945 })
4946 .on('click', function(d,i) {
4947 if (!interactive) return;
4948 dispatch.elementClick({
4949 data: d,
4950 index: i,
4951 color: d3.select(this).style("fill")
4952 });
4953 d3.event.stopPropagation();
4954 })
4955 .on('dblclick', function(d,i) {
4956 if (!interactive) return;
4957 dispatch.elementDblClick({
4958 data: d,
4959 index: i,
4960 color: d3.select(this).style("fill")
4961 });
4962 d3.event.stopPropagation();
4963 });
4965 bars
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 ?
4976 y(0) :
4977 y(0) - y(getY(d,i)) < 1 ?
4978 y(0) - 1 :
4979 y(getY(d,i));
4980 return nv.utils.NaNtoZero(rval);
4981 })
4982 .attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
4984 });
4986 renderWatch.renderEnd('historicalBar immediate');
4987 return chart;
4990 //Create methods to allow outside functions to highlight a specific bar.
4991 chart.highlightPoint = function(pointIndex, isHoverOver) {
4992 container
4993 .select(".nv-bars .nv-bar-0-" + pointIndex)
4994 .classed("hover", isHoverOver)
4996 };
4998 chart.clearHighlights = function() {
4999 container
5000 .select(".nv-bars .nv-bar.hover")
5001 .classed("hover", false)
5003 };
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;
5037 }},
5038 color: {get: function(){return color;}, set: function(_){
5039 color = nv.utils.getColor(_);
5040 }}
5041 });
5043 nv.utils.initOptions(chart);
5045 return chart;
5046 };
5048 nv.models.historicalBarChart = function(bar_model) {
5049 "use strict";
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()
5066 , width = null
5067 , height = null
5068 , showLegend = false
5069 , showXAxis = true
5070 , showYAxis = true
5071 , rightAlignYAxis = false
5072 , useInteractiveGuideline = false
5073 , x
5074 , y
5075 , state = {}
5076 , defaultState = null
5077 , noData = null
5078 , dispatch = d3.dispatch('tooltipHide', 'stateChange', 'changeState', 'renderEnd')
5079 , transitionDuration = 250
5082 xAxis.orient('bottom').tickPadding(7);
5083 yAxis.orient( (rightAlignYAxis) ? 'right' : 'left');
5084 tooltip
5085 .duration(0)
5086 .headerEnabled(false)
5087 .valueFormatter(function(d, i) {
5088 return yAxis.tickFormat()(d, i);
5089 })
5090 .headerFormatter(function(d, i) {
5091 return xAxis.tickFormat()(d, i);
5092 });
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),
5109 that = 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) {
5121 var key;
5122 defaultState = {};
5123 for (key in state) {
5124 if (state[key] instanceof Array)
5125 defaultState[key] = state[key].slice(0);
5126 else
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)
5134 return chart;
5135 } else {
5136 container.selectAll('.nv-noData').remove();
5139 // Setup Scales
5140 x = bars.xScale();
5141 y = bars.yScale();
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');
5154 // Legend
5155 if (showLegend) {
5156 legend.width(availableWidth);
5158 g.select('.nv-legendWrap')
5159 .datum(data)
5160 .call(legend);
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) {
5179 interactiveLayer
5180 .width(availableWidth)
5181 .height(availableHeight)
5182 .margin({left:margin.left, top:margin.top})
5183 .svgContainer(container)
5184 .xScale(x);
5185 wrap.select(".nv-interactive").call(interactiveLayer);
5187 bars
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);
5198 // Setup Axes
5199 if (showXAxis) {
5200 xAxis
5201 .scale(x)
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')
5208 .transition()
5209 .call(xAxis);
5212 if (showYAxis) {
5213 yAxis
5214 .scale(y)
5215 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
5216 .tickSize( -availableWidth, 0);
5218 g.select('.nv-y.nv-axis')
5219 .transition()
5220 .call(yAxis);
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 = [];
5231 data
5232 .filter(function(series, i) {
5233 series.seriesIndex = i;
5234 return !series.disabled;
5235 })
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));
5243 allData.push({
5244 key: series.key,
5245 value: chart.y()(point, pointIndex),
5246 color: color(series,series.seriesIndex),
5247 data: series.values[pointIndex]
5248 });
5249 });
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);
5257 })
5258 .data({
5259 value: xValue,
5260 index: pointIndex,
5261 series: allData
5262 })();
5264 interactiveLayer.renderGuideLine(pointXLocation);
5266 });
5268 interactiveLayer.dispatch.on("elementMouseout",function(e) {
5269 dispatch.tooltipHide();
5270 bars.clearHighlights();
5271 });
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) {
5278 d.disabled = false;
5279 wrap.selectAll('.nv-series').classed('disabled', false);
5280 return d;
5281 });
5284 state.disabled = data.map(function(d) { return !!d.disabled });
5285 dispatch.stateChange(state);
5287 selection.transition().call(chart);
5288 });
5290 legend.dispatch.on('legendDblclick', function(d) {
5291 //Double clicking should always enable current series, and disabled all others.
5292 data.forEach(function(d) {
5293 d.disabled = true;
5294 });
5295 d.disabled = false;
5297 state.disabled = data.map(function(d) { return !!d.disabled });
5298 dispatch.stateChange(state);
5299 chart.update();
5300 });
5302 dispatch.on('changeState', function(e) {
5303 if (typeof e.disabled !== 'undefined') {
5304 data.forEach(function(series,i) {
5305 series.disabled = e.disabled[i];
5306 });
5308 state.disabled = e.disabled;
5311 chart.update();
5312 });
5313 });
5315 renderWatch.renderEnd('historicalBarChart immediate');
5316 return chart;
5319 //============================================================
5320 // Event Handling/Dispatching (out of chart's scope)
5321 //------------------------------------------------------------
5323 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
5324 evt['series'] = {
5325 key: chart.x()(evt.data),
5326 value: chart.y()(evt.data),
5327 color: evt.color
5328 };
5329 tooltip.data(evt).hidden(false);
5330 });
5332 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
5333 tooltip.hidden(true);
5334 });
5336 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
5337 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
5338 });
5340 //============================================================
5341 // Expose Public Variables
5342 //------------------------------------------------------------
5344 // expose chart's sub-components
5345 chart.dispatch = dispatch;
5346 chart.bars = bars;
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(!!_);
5370 }},
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(_);
5375 }},
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;
5383 }},
5384 color: {get: function(){return color;}, set: function(_){
5385 color = nv.utils.getColor(_);
5386 legend.color(color);
5387 bars.color(color);
5388 }},
5389 duration: {get: function(){return transitionDuration;}, set: function(_){
5390 transitionDuration=_;
5391 renderWatch.reset(transitionDuration);
5392 yAxis.duration(transitionDuration);
5393 xAxis.duration(transitionDuration);
5394 }},
5395 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
5396 rightAlignYAxis = _;
5397 yAxis.orient( (_) ? 'right' : 'left');
5398 }},
5399 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
5400 useInteractiveGuideline = _;
5401 if (_ === true) {
5402 chart.interactive(false);
5404 }}
5405 });
5407 nv.utils.inheritOptions(chart, bars);
5408 nv.utils.initOptions(chart);
5410 return chart;
5411 };
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";
5425 return '' +
5426 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
5427 '<table>' +
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>' +
5432 '</table>';
5433 });
5434 return chart;
5435 };
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";
5448 return '' +
5449 '<h3 style="color: #' + color + '">' + data.value + '</h3>' +
5450 '<table>' +
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>' +
5455 '</table>';
5456 });
5457 return chart;
5458 };
5459 nv.models.legend = function() {
5460 "use strict";
5462 //============================================================
5463 // Public Variables with Default Settings
5464 //------------------------------------------------------------
5466 var margin = {top: 5, right: 0, bottom: 5, left: 0}
5467 , width = 400
5468 , height = 20
5469 , getKey = function(d) { return d.key }
5470 , color = nv.utils.getColor()
5471 , align = true
5472 , padding = 32 //define how much space between legend items. - recommend 32 for furious version
5473 , rightAlign = true
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)
5476 , expanded = false
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')
5495 .data(function(d) {
5496 if(vers != 'furious') return d;
5498 return d.filter(function(n) {
5499 return expanded ? true : !n.disengaged;
5500 });
5501 });
5503 var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
5504 var seriesShape;
5506 var versPadding;
5507 switch(vers) {
5508 case 'furious' :
5509 versPadding = 23;
5510 break;
5511 case 'classic' :
5512 versPadding = 20;
5515 if(vers == 'classic') {
5516 seriesEnter.append('circle')
5517 .style('stroke-width', 2)
5518 .attr('class','nv-legend-symbol')
5519 .attr('r', 5);
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')
5526 .attr('rx', 3)
5527 .attr('ry', 3);
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));
5541 });
5544 seriesEnter.append('text')
5545 .attr('text-anchor', 'start')
5546 .attr('class','nv-legend-text')
5547 .attr('dy', '.32em')
5548 .attr('dx', '8');
5550 var seriesText = series.select('text.nv-legend-text');
5552 series
5553 .on('mouseover', function(d,i) {
5554 dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
5555 })
5556 .on('mouseout', function(d,i) {
5557 dispatch.legendMouseout(d,i);
5558 })
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();
5563 if (updateState) {
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});
5569 d.disabled = false;
5571 else {
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') {
5580 if(expanded) {
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;
5593 });
5597 dispatch.stateChange({
5598 disabled: data.map(function(d) { return !!d.disabled }),
5599 disengaged: data.map(function(d) { return !!d.disengaged })
5600 });
5603 })
5604 .on('dblclick', function(d,i) {
5605 if(vers == 'furious' && expanded) return;
5606 dispatch.legendDblclick(d,i);
5607 if (updateState) {
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;
5615 });
5616 d.disabled = false;
5617 if(vers == 'furious') d.userDisabled = d.disabled;
5618 dispatch.stateChange({
5619 disabled: data.map(function(d) { return !!d.disabled })
5620 });
5622 });
5624 series.classed('nv-disabled', function(d) { return d.userDisabled });
5625 series.exit().remove();
5627 seriesText
5628 .attr('fill', setTextColor)
5629 .text(getKey);
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;
5634 if (align) {
5636 var seriesWidths = [];
5637 series.each(function(d,i) {
5638 var legendText = d3.select(this).select('text');
5639 var nodeTextLength;
5640 try {
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();
5645 catch(e) {
5646 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
5649 seriesWidths.push(nodeTextLength + padding);
5650 });
5652 var seriesPerRow = 0;
5653 var columnWidths = [];
5654 legendWidth = 0;
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 ) {
5663 columnWidths = [];
5664 seriesPerRow--;
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) {
5672 return prev + cur;
5673 });
5676 var xPositions = [];
5677 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
5678 xPositions[i] = curX;
5679 curX += columnWidths[i];
5682 series
5683 .attr('transform', function(d, i) {
5684 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
5685 });
5687 //position legend as far right as possible within the total width
5688 if (rightAlign) {
5689 g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
5691 else {
5692 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5695 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
5697 } else {
5699 var ypos = 5,
5700 newxpos = 5,
5701 maxwidth = 0,
5702 xpos;
5703 series
5704 .attr('transform', function(d, i) {
5705 var length = d3.select(this).select('text').node().getComputedTextLength() + padding;
5706 xpos = newxpos;
5708 if (width < margin.left + margin.right + xpos + length) {
5709 newxpos = xpos = 5;
5710 ypos += versPadding;
5713 newxpos += length;
5714 if (newxpos > maxwidth) maxwidth = newxpos;
5716 if(legendWidth < xpos + maxwidth) {
5717 legendWidth = xpos + maxwidth;
5719 return 'translate(' + xpos + ',' + ypos + ')';
5720 });
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
5730 seriesShape
5731 .attr('width', function(d,i) {
5732 return seriesText[0][i].getComputedTextLength() + 27;
5733 })
5734 .attr('height', 18)
5735 .attr('y', -9)
5736 .attr('x', -15);
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')
5743 .attr('opacity',0);
5745 var seriesBG = g.select('.nv-legend-bg');
5747 seriesBG
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);
5758 seriesShape
5759 .style('fill', setBGColor)
5760 .style('fill-opacity', setBGOpacity)
5761 .style('stroke', setBGColor);
5762 });
5764 function setTextColor(d,i) {
5765 if(vers != 'furious') return '#000';
5766 if(expanded) {
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);
5777 } else {
5778 return d.color || color(d,i);
5783 function setBGOpacity(d,i) {
5784 if(expanded && vers == 'furious') {
5785 return 1;
5786 } else {
5787 return !!d.disabled ? 0 : 1;
5791 return chart;
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;
5820 }},
5821 color: {get: function(){return color;}, set: function(_){
5822 color = nv.utils.getColor(_);
5823 }}
5824 });
5826 nv.utils.initOptions(chart);
5828 return chart;
5829 };
5831 nv.models.line = function() {
5832 "use strict";
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}
5841 , width = 960
5842 , height = 500
5843 , container = null
5844 , strokeWidth = 1.5
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
5854 , duration = 250
5855 , dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
5858 scatter
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);
5886 // Setup Scales
5887 x = scatter.xScale();
5888 y = scatter.yScale();
5890 x0 = x0 || x;
5891 y0 = y0 || y;
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 + ')');
5905 scatter
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())
5914 .append('rect');
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() + ')' : '');
5921 scatterWrap
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();
5933 groups
5934 .attr('class', function(d,i) {
5935 return (d.classed || '') + ' nv-group nv-series-' + i;
5936 })
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)
5951 .defined(defined)
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])
5957 });
5958 groups.exit().selectAll('path.nv-area')
5959 .remove();
5961 areaPaths.watchTransition(renderWatch, 'line: areaPaths')
5962 .attr('d', function(d) {
5963 return d3.svg.area()
5964 .interpolate(interpolate)
5965 .defined(defined)
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])
5971 });
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')
5978 .attr('d',
5979 d3.svg.line()
5980 .interpolate(interpolate)
5981 .defined(defined)
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))) })
5984 );
5986 linePaths.watchTransition(renderWatch, 'line: linePaths')
5987 .attr('d',
5988 d3.svg.line()
5989 .interpolate(interpolate)
5990 .defined(defined)
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))) })
5993 );
5995 //store old scales for use in transitions on update
5996 x0 = x.copy();
5997 y0 = y.copy();
5998 });
5999 renderWatch.renderEnd('line immediate');
6000 return chart;
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;
6031 }},
6032 duration: {get: function(){return duration;}, set: function(_){
6033 duration = _;
6034 renderWatch.reset(duration);
6035 scatter.duration(duration);
6036 }},
6037 isArea: {get: function(){return isArea;}, set: function(_){
6038 isArea = d3.functor(_);
6039 }},
6040 x: {get: function(){return getX;}, set: function(_){
6041 getX = _;
6042 scatter.x(_);
6043 }},
6044 y: {get: function(){return getY;}, set: function(_){
6045 getY = _;
6046 scatter.y(_);
6047 }},
6048 color: {get: function(){return color;}, set: function(_){
6049 color = nv.utils.getColor(_);
6050 scatter.color(color);
6051 }}
6052 });
6054 nv.utils.inheritOptions(chart, scatter);
6055 nv.utils.initOptions(chart);
6057 return chart;
6058 };
6059 nv.models.lineChart = function() {
6060 "use strict";
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()
6076 , width = null
6077 , height = null
6078 , showLegend = true
6079 , showXAxis = true
6080 , showYAxis = true
6081 , rightAlignYAxis = false
6082 , useInteractiveGuideline = false
6083 , x
6084 , y
6085 , state = nv.utils.state()
6086 , defaultState = null
6087 , noData = null
6088 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState', 'renderEnd')
6089 , duration = 250
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);
6099 });
6102 //============================================================
6103 // Private Variables
6104 //------------------------------------------------------------
6106 var renderWatch = nv.utils.renderWatch(dispatch, duration);
6108 var stateGetter = function(data) {
6109 return function(){
6110 return {
6111 active: data.map(function(d) { return !d.disabled })
6112 };
6114 };
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];
6121 });
6123 };
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),
6133 that = 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() {
6139 if (duration === 0)
6140 container.call(chart);
6141 else
6142 container.transition().duration(duration).call(chart)
6143 };
6144 chart.container = this;
6146 state
6147 .setter(stateSetter(data), chart.update)
6148 .getter(stateGetter(data))
6149 .update();
6151 // DEPRECATED set state.disableddisabled
6152 state.disabled = data.map(function(d) { return !!d.disabled });
6154 if (!defaultState) {
6155 var key;
6156 defaultState = {};
6157 for (key in state) {
6158 if (state[key] instanceof Array)
6159 defaultState[key] = state[key].slice(0);
6160 else
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)
6168 return chart;
6169 } else {
6170 container.selectAll('.nv-noData').remove();
6174 // Setup Scales
6175 x = lines.xScale();
6176 y = lines.yScale();
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');
6190 g.select("rect")
6191 .attr("width",availableWidth)
6192 .attr("height",(availableHeight > 0) ? availableHeight : 0);
6194 // Legend
6195 if (showLegend) {
6196 legend.width(availableWidth);
6198 g.select('.nv-legendWrap')
6199 .datum(data)
6200 .call(legend);
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) {
6220 interactiveLayer
6221 .width(availableWidth)
6222 .height(availableHeight)
6223 .margin({left:margin.left, top:margin.top})
6224 .svgContainer(container)
6225 .xScale(x);
6226 wrap.select(".nv-interactive").call(interactiveLayer);
6229 lines
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);
6242 // Setup Axes
6243 if (showXAxis) {
6244 xAxis
6245 .scale(x)
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')
6252 .call(xAxis);
6255 if (showYAxis) {
6256 yAxis
6257 .scale(y)
6258 ._ticks(nv.utils.calcTicksY(availableHeight/36, data) )
6259 .tickSize( -availableWidth, 0);
6261 g.select('.nv-y.nv-axis')
6262 .call(yAxis);
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);
6273 chart.update();
6274 });
6276 interactiveLayer.dispatch.on('elementMousemove', function(e) {
6277 lines.clearHighlights();
6278 var singlePoint, pointIndex, pointXLocation, allData = [];
6279 data
6280 .filter(function(series, i) {
6281 series.seriesIndex = i;
6282 return !series.disabled;
6283 })
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));
6294 allData.push({
6295 key: series.key,
6296 value: pointYValue,
6297 color: color(series,series.seriesIndex)
6298 });
6299 });
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);
6316 })
6317 .data({
6318 value: xValue,
6319 index: pointIndex,
6320 series: allData
6321 })();
6323 interactiveLayer.renderGuideLine(pointXLocation);
6325 });
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));
6339 allData.push({
6340 point: point,
6341 pointIndex: pointIndex,
6342 pos: [pointXLocation, yPos],
6343 seriesIndex: series.seriesIndex,
6344 series: series
6345 });
6346 });
6348 lines.dispatch.elementClick(allData);
6349 });
6351 interactiveLayer.dispatch.on("elementMouseout",function(e) {
6352 lines.clearHighlights();
6353 });
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];
6359 });
6361 state.disabled = e.disabled;
6364 chart.update();
6365 });
6367 });
6369 renderWatch.renderEnd('lineChart immediate');
6370 return chart;
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);
6379 });
6381 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6382 tooltip.hidden(true)
6383 });
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(!!_);
6416 }},
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(_);
6421 }},
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;
6429 }},
6430 duration: {get: function(){return duration;}, set: function(_){
6431 duration = _;
6432 renderWatch.reset(duration);
6433 lines.duration(duration);
6434 xAxis.duration(duration);
6435 yAxis.duration(duration);
6436 }},
6437 color: {get: function(){return color;}, set: function(_){
6438 color = nv.utils.getColor(_);
6439 legend.color(color);
6440 lines.color(color);
6441 }},
6442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
6443 rightAlignYAxis = _;
6444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
6445 }},
6446 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
6447 useInteractiveGuideline = _;
6448 if (useInteractiveGuideline) {
6449 lines.interactive(false);
6450 lines.useVoronoi(false);
6452 }}
6453 });
6455 nv.utils.inheritOptions(chart, lines);
6456 nv.utils.initOptions(chart);
6458 return chart;
6459 };
6460 nv.models.linePlusBarChart = function() {
6461 "use strict";
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}
6484 , width = null
6485 , height = null
6486 , getX = function(d) { return d.x }
6487 , getY = function(d) { return d.y }
6488 , color = nv.utils.defaultColor()
6489 , showLegend = true
6490 , focusEnable = true
6491 , focusShowAxisY = false
6492 , focusShowAxisX = true
6493 , focusHeight = 50
6494 , extent
6495 , brushExtent = null
6496 , x
6497 , x2
6498 , y1
6499 , y2
6500 , y3
6501 , y4
6502 , noData = 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);
6522 });
6524 //============================================================
6525 // Private Variables
6526 //------------------------------------------------------------
6528 var stateGetter = function(data) {
6529 return function(){
6530 return {
6531 active: data.map(function(d) { return !d.disabled })
6532 };
6534 };
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];
6541 });
6543 };
6545 function chart(selection) {
6546 selection.each(function(data) {
6547 var container = d3.select(this),
6548 that = 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;
6558 state
6559 .setter(stateSetter(data), chart.update)
6560 .getter(stateGetter(data))
6561 .update();
6563 // DEPRECATED set state.disableddisabled
6564 state.disabled = data.map(function(d) { return !!d.disabled });
6566 if (!defaultState) {
6567 var key;
6568 defaultState = {};
6569 for (key in state) {
6570 if (state[key] instanceof Array)
6571 defaultState[key] = state[key].slice(0);
6572 else
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)
6580 return chart;
6581 } else {
6582 container.selectAll('.nv-noData').remove();
6585 // Setup Scales
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
6589 x = bars.xScale();
6590 x2 = x2Axis.scale();
6591 y1 = bars.yScale();
6592 y2 = lines.yScale();
6593 y3 = bars2.yScale();
6594 y4 = lines2.yScale();
6596 var series1 = data
6597 .filter(function(d) { return !d.disabled && d.bar })
6598 .map(function(d) {
6599 return d.values.map(function(d,i) {
6600 return { x: getX(d,i), y: getY(d,i) }
6601 })
6602 });
6604 var series2 = data
6605 .filter(function(d) { return !d.disabled && !d.bar })
6606 .map(function(d) {
6607 return d.values.map(function(d,i) {
6608 return { x: getX(d,i), y: getY(d,i) }
6609 })
6610 });
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 //============================================================
6643 // Legend
6644 //------------------------------------------------------------
6646 if (showLegend) {
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);
6656 return series;
6657 }))
6658 .call(legend);
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');
6679 bars2
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
6686 }));
6687 lines2
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
6694 }));
6696 var bars2Wrap = g.select('.nv-context .nv-barsWrap')
6697 .datum(dataBars.length ? dataBars : [
6698 {values: []}
6699 ]);
6700 var lines2Wrap = g.select('.nv-context .nv-linesWrap')
6701 .datum(!dataLines[0].disabled ? dataLines : [
6702 {values: []}
6703 ]);
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) {
6713 x2Axis
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()
6719 .call(x2Axis);
6722 if (focusShowAxisY) {
6723 y3Axis
6724 .scale(y3)
6725 ._ticks( availableHeight2 / 36 )
6726 .tickSize( -availableWidth, 0);
6727 y4Axis
6728 .scale(y4)
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()
6740 .call(y3Axis);
6741 g.select('.nv-context .nv-y2.nv-axis').transition()
6742 .call(y4Axis);
6745 // Setup Brush
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()
6754 .append('g');
6756 brushBGenter.append('rect')
6757 .attr('class', 'left')
6758 .attr('x', 0)
6759 .attr('y', 0)
6760 .attr('height', availableHeight2);
6762 brushBGenter.append('rect')
6763 .attr('class', 'right')
6764 .attr('x', 0)
6765 .attr('y', 0)
6766 .attr('height', availableHeight2);
6768 var gBrush = g.select('.nv-x.nv-brush')
6769 .call(brush);
6770 gBrush.selectAll('rect')
6771 //.attr('y', -5)
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);
6783 chart.update();
6784 });
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];
6791 });
6792 state.disabled = e.disabled;
6794 chart.update();
6795 });
6797 //============================================================
6798 // Functions
6799 //------------------------------------------------------------
6801 // Taken from crossfilter (http://square.github.com/crossfilter/)
6802 function resizePath(d) {
6803 var e = +(d == 'e'),
6804 x = e ? 1 : -1,
6805 y = availableHeight2 / 3;
6806 return 'M' + (.5 * x) + ',' + y
6807 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6808 + 'V' + (2 * y - 6)
6809 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6810 + 'Z'
6811 + 'M' + (2.5 * x) + ',' + (y + 8)
6812 + 'V' + (2 * y - 8)
6813 + 'M' + (4.5 * x) + ',' + (y + 8)
6814 + 'V' + (2 * y - 8);
6818 function updateBrushBG() {
6819 if (!brush.empty()) brush.extent(brushExtent);
6820 brushBG
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);
6831 });
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});
6838 updateBrushBG();
6840 // Prepare Main (Focus) Bars and Lines
6841 bars
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 }));
6848 lines
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:[]}] :
6857 dataBars
6858 .map(function(d,i) {
6859 return {
6860 key: d.key,
6861 values: d.values.filter(function(d,i) {
6862 return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
6863 })
6865 })
6866 );
6868 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6869 .datum(dataLines[0].disabled ? [{values:[]}] :
6870 dataLines
6871 .map(function(d,i) {
6872 return {
6873 area: d.area,
6874 fillOpacity: d.fillOpacity,
6875 key: d.key,
6876 values: d.values.filter(function(d,i) {
6877 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6878 })
6880 })
6881 );
6883 // Update Main (Focus) X Axis
6884 if (dataBars.length) {
6885 x = bars.xScale();
6886 } else {
6887 x = lines.xScale();
6890 xAxis
6891 .scale(x)
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)
6898 .call(xAxis);
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] + ')');
6908 y1Axis
6909 .scale(y1)
6910 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
6911 .tickSize(-availableWidth, 0);
6912 y2Axis
6913 .scale(y2)
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)
6924 .call(y1Axis);
6925 g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
6926 .call(y2Axis);
6929 onBrush();
6931 });
6933 return chart;
6936 //============================================================
6937 // Event Handling/Dispatching (out of chart's scope)
6938 //------------------------------------------------------------
6940 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
6941 tooltip
6942 .duration(100)
6943 .valueFormatter(function(d, i) {
6944 return y2Axis.tickFormat()(d, i);
6945 })
6946 .data(evt)
6947 .position(evt.pos)
6948 .hidden(false);
6949 });
6951 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6952 tooltip.hidden(true)
6953 });
6955 bars.dispatch.on('elementMouseover.tooltip', function(evt) {
6956 evt.value = chart.x()(evt.data);
6957 evt['series'] = {
6958 value: chart.y()(evt.data),
6959 color: evt.color
6960 };
6961 tooltip
6962 .duration(0)
6963 .valueFormatter(function(d, i) {
6964 return y1Axis.tickFormat()(d, i);
6965 })
6966 .data(evt)
6967 .hidden(false);
6968 });
6970 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
6971 tooltip.hidden(true);
6972 });
6974 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
6975 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
6976 });
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;
6990 chart.bars = bars;
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(!!_);
7021 }},
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(_);
7026 }},
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;
7034 }},
7035 duration: {get: function(){return transitionDuration;}, set: function(_){
7036 transitionDuration = _;
7037 }},
7038 color: {get: function(){return color;}, set: function(_){
7039 color = nv.utils.getColor(_);
7040 legend.color(color);
7041 }},
7042 x: {get: function(){return getX;}, set: function(_){
7043 getX = _;
7044 lines.x(_);
7045 lines2.x(_);
7046 bars.x(_);
7047 bars2.x(_);
7048 }},
7049 y: {get: function(){return getY;}, set: function(_){
7050 getY = _;
7051 lines.y(_);
7052 lines2.y(_);
7053 bars.y(_);
7054 bars2.y(_);
7055 }}
7056 });
7058 nv.utils.inheritOptions(chart, lines);
7059 nv.utils.initOptions(chart);
7061 return chart;
7062 };
7063 nv.models.lineWithFocusChart = function() {
7064 "use strict";
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()
7085 , width = null
7086 , height = null
7087 , height2 = 50
7088 , useInteractiveGuideline = false
7089 , x
7090 , y
7091 , x2
7092 , y2
7093 , showLegend = true
7094 , brushExtent = null
7095 , noData = 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);
7113 });
7115 //============================================================
7116 // Private Variables
7117 //------------------------------------------------------------
7119 var stateGetter = function(data) {
7120 return function(){
7121 return {
7122 active: data.map(function(d) { return !d.disabled })
7123 };
7125 };
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];
7132 });
7134 };
7136 function chart(selection) {
7137 selection.each(function(data) {
7138 var container = d3.select(this),
7139 that = 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;
7148 state
7149 .setter(stateSetter(data), chart.update)
7150 .getter(stateGetter(data))
7151 .update();
7153 // DEPRECATED set state.disableddisabled
7154 state.disabled = data.map(function(d) { return !!d.disabled });
7156 if (!defaultState) {
7157 var key;
7158 defaultState = {};
7159 for (key in state) {
7160 if (state[key] instanceof Array)
7161 defaultState[key] = state[key].slice(0);
7162 else
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)
7170 return chart;
7171 } else {
7172 container.selectAll('.nv-noData').remove();
7175 // Setup Scales
7176 x = lines.xScale();
7177 y = lines.yScale();
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');
7201 // Legend
7202 if (showLegend) {
7203 legend.width(availableWidth);
7205 g.select('.nv-legendWrap')
7206 .datum(data)
7207 .call(legend);
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) {
7223 interactiveLayer
7224 .width(availableWidth)
7225 .height(availableHeight1)
7226 .margin({left:margin.left, top:margin.top})
7227 .svgContainer(container)
7228 .xScale(x);
7229 wrap.select(".nv-interactive").call(interactiveLayer);
7232 // Main Chart Component(s)
7233 lines
7234 .width(availableWidth)
7235 .height(availableHeight1)
7236 .color(
7237 data
7238 .map(function(d,i) {
7239 return d.color || color(d, i);
7240 })
7241 .filter(function(d,i) {
7242 return !data[i].disabled;
7243 })
7244 );
7246 lines2
7247 .defined(lines.defined())
7248 .width(availableWidth)
7249 .height(availableHeight2)
7250 .color(
7251 data
7252 .map(function(d,i) {
7253 return d.color || color(d, i);
7254 })
7255 .filter(function(d,i) {
7256 return !data[i].disabled;
7257 })
7258 );
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
7269 xAxis
7270 .scale(x)
7271 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7272 .tickSize(-availableHeight1, 0);
7274 yAxis
7275 .scale(y)
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 + ')');
7282 // Setup Brush
7283 brush
7284 .x(x2)
7285 .on('brush', function() {
7286 onBrush();
7287 });
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()
7295 .append('g');
7297 brushBGenter.append('rect')
7298 .attr('class', 'left')
7299 .attr('x', 0)
7300 .attr('y', 0)
7301 .attr('height', availableHeight2);
7303 brushBGenter.append('rect')
7304 .attr('class', 'right')
7305 .attr('x', 0)
7306 .attr('y', 0)
7307 .attr('height', availableHeight2);
7309 var gBrush = g.select('.nv-x.nv-brush')
7310 .call(brush);
7311 gBrush.selectAll('rect')
7312 .attr('height', availableHeight2);
7313 gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7315 onBrush();
7317 // Setup Secondary (Context) Axes
7318 x2Axis
7319 .scale(x2)
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'))
7326 .call(x2Axis);
7328 y2Axis
7329 .scale(y2)
7330 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
7331 .tickSize( -availableWidth, 0);
7333 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
7334 .call(y2Axis);
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);
7347 chart.update();
7348 });
7350 interactiveLayer.dispatch.on('elementMousemove', function(e) {
7351 lines.clearHighlights();
7352 var singlePoint, pointIndex, pointXLocation, allData = [];
7353 data
7354 .filter(function(series, i) {
7355 series.seriesIndex = i;
7356 return !series.disabled;
7357 })
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];
7362 });
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));
7373 allData.push({
7374 key: series.key,
7375 value: chart.y()(point, pointIndex),
7376 color: color(series,series.seriesIndex)
7377 });
7378 });
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);
7395 })
7396 .data({
7397 value: xValue,
7398 index: pointIndex,
7399 series: allData
7400 })();
7402 interactiveLayer.renderGuideLine(pointXLocation);
7404 });
7406 interactiveLayer.dispatch.on("elementMouseout",function(e) {
7407 lines.clearHighlights();
7408 });
7410 dispatch.on('changeState', function(e) {
7411 if (typeof e.disabled !== 'undefined') {
7412 data.forEach(function(series,i) {
7413 series.disabled = e.disabled[i];
7414 });
7416 chart.update();
7417 });
7419 //============================================================
7420 // Functions
7421 //------------------------------------------------------------
7423 // Taken from crossfilter (http://square.github.com/crossfilter/)
7424 function resizePath(d) {
7425 var e = +(d == 'e'),
7426 x = e ? 1 : -1,
7427 y = availableHeight2 / 3;
7428 return 'M' + (.5 * x) + ',' + y
7429 + 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7430 + 'V' + (2 * y - 6)
7431 + 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7432 + 'Z'
7433 + 'M' + (2.5 * x) + ',' + (y + 8)
7434 + 'V' + (2 * y - 8)
7435 + 'M' + (4.5 * x) + ',' + (y + 8)
7436 + 'V' + (2 * y - 8);
7440 function updateBrushBG() {
7441 if (!brush.empty()) brush.extent(brushExtent);
7442 brushBG
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);
7453 });
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) {
7463 return;
7466 dispatch.brush({extent: extent, brush: brush});
7469 updateBrushBG();
7471 // Update Main (Focus)
7472 var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7473 .datum(
7474 data
7475 .filter(function(d) { return !d.disabled })
7476 .map(function(d,i) {
7477 return {
7478 key: d.key,
7479 area: d.area,
7480 values: d.values.filter(function(d,i) {
7481 return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7482 })
7484 })
7485 );
7486 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7489 // Update Main (Focus) Axes
7490 g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
7491 .call(xAxis);
7492 g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
7493 .call(yAxis);
7495 });
7497 return chart;
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);
7506 });
7508 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
7509 tooltip.hidden(true)
7510 });
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(!!_);
7545 }},
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(_);
7550 }},
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;
7558 }},
7559 color: {get: function(){return color;}, set: function(_){
7560 color = nv.utils.getColor(_);
7561 legend.color(color);
7562 // line color is handled above?
7563 }},
7564 interpolate: {get: function(){return lines.interpolate();}, set: function(_){
7565 lines.interpolate(_);
7566 lines2.interpolate(_);
7567 }},
7568 xTickFormat: {get: function(){return xAxis.tickFormat();}, set: function(_){
7569 xAxis.tickFormat(_);
7570 x2Axis.tickFormat(_);
7571 }},
7572 yTickFormat: {get: function(){return yAxis.tickFormat();}, set: function(_){
7573 yAxis.tickFormat(_);
7574 y2Axis.tickFormat(_);
7575 }},
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);
7582 }},
7583 x: {get: function(){return lines.x();}, set: function(_){
7584 lines.x(_);
7585 lines2.x(_);
7586 }},
7587 y: {get: function(){return lines.y();}, set: function(_){
7588 lines.y(_);
7589 lines2.y(_);
7590 }},
7591 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
7592 useInteractiveGuideline = _;
7593 if (useInteractiveGuideline) {
7594 lines.interactive(false);
7595 lines.useVoronoi(false);
7597 }}
7598 });
7600 nv.utils.inheritOptions(chart, lines);
7601 nv.utils.initOptions(chart);
7603 return chart;
7604 };
7606 nv.models.multiBar = function() {
7607 "use strict";
7609 //============================================================
7610 // Public Variables with Default Settings
7611 //------------------------------------------------------------
7613 var margin = {top: 0, right: 0, bottom: 0, left: 0}
7614 , width = 960
7615 , height = 500
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
7619 , container = null
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
7623 , clipEdge = true
7624 , stacked = false
7625 , stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
7626 , color = nv.utils.defaultColor()
7627 , hideable = false
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
7630 , duration = 500
7631 , xDomain
7632 , yDomain
7633 , xRange
7634 , yRange
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)
7661 return true;
7662 return false;
7663 };
7665 if(hideable && data.length) hideable = [{
7666 values: data[0].values.map(function(d) {
7667 return {
7668 x: d.x,
7669 y: 0,
7670 series: d.series,
7671 size: 0.01
7672 };}
7673 )}];
7675 if (stacked) {
7676 var parsed = d3.layout.stack()
7677 .offset(stackOffset)
7678 .values(function(d){ return d.values })
7679 .y(getY)
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];
7687 } else {
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;
7692 d.y1 = d.y0 + d.y;
7693 });
7696 });
7697 data = parsed;
7699 //add series index and key to each data point for reference
7700 data.forEach(function(series, i) {
7701 series.values.forEach(function(point) {
7702 point.series = i;
7703 point.key = series.key;
7704 });
7705 });
7707 // HACK for negative value stacking
7708 if (stacked) {
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) {
7713 var f = d.values[i]
7714 f.size = Math.abs(f.y);
7715 if (f.y<0) {
7716 f.y1 = negBase;
7717 negBase = negBase - f.size;
7718 } else
7720 f.y1 = f.size + posBase;
7721 posBase = posBase + f.size;
7725 });
7726 });
7728 // Setup Scales
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 }
7734 })
7735 });
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) {
7741 var domain = d.y;
7742 // increase the domain range if this series is stackable
7743 if (stacked && !data[d.idx].nonStackable) {
7744 if (d.y > 0){
7745 domain = d.y1
7746 } else {
7747 domain = d.y1 + d.y
7750 return domain;
7751 }).concat(forceY)))
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])
7756 x.domain()[0] ?
7757 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7758 : x.domain([-1,1]);
7760 if (y.domain()[0] === y.domain()[1])
7761 y.domain()[0] ?
7762 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7763 : y.domain([-1,1]);
7765 x0 = x0 || x;
7766 y0 = y0 || y;
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)
7780 .append('rect');
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;
7797 if (stacked) {
7798 if (data[d.series] && !data[d.series].nonStackable) {
7799 yVal = y0(d.y0);
7802 return yVal;
7803 })
7804 .attr('height', 0)
7805 .remove();
7806 if (exitTransition.delay)
7807 exitTransition.delay(function(d,i) {
7808 var delay = i * (duration / (last_datalength + 1)) - i;
7809 return delay;
7810 });
7811 groups
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) });
7816 groups
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 )
7828 })
7829 .attr('y', function(d,i,j) { return y0(stacked && !data[j].nonStackable ? d.y0 : 0) || 0 })
7830 .attr('height', 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)'; })
7834 bars
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({
7840 data: d,
7841 index: i,
7842 color: d3.select(this).style("fill")
7843 });
7844 })
7845 .on('mouseout', function(d,i) {
7846 d3.select(this).classed('hover', false);
7847 dispatch.elementMouseout({
7848 data: d,
7849 index: i,
7850 color: d3.select(this).style("fill")
7851 });
7852 })
7853 .on('mousemove', function(d,i) {
7854 dispatch.elementMousemove({
7855 data: d,
7856 index: i,
7857 color: d3.select(this).style("fill")
7858 });
7859 })
7860 .on('click', function(d,i) {
7861 dispatch.elementClick({
7862 data: d,
7863 index: i,
7864 color: d3.select(this).style("fill")
7865 });
7866 d3.event.stopPropagation();
7867 })
7868 .on('dblclick', function(d,i) {
7869 dispatch.elementDblClick({
7870 data: d,
7871 index: i,
7872 color: d3.select(this).style("fill")
7873 });
7874 d3.event.stopPropagation();
7875 });
7876 bars
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)'; })
7880 if (barColor) {
7881 if (!disabled) disabled = data.map(function() { return true });
7882 bars
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(); });
7887 var barSelection =
7888 bars.watchTransition(renderWatch, 'multibar', Math.min(250, duration))
7889 .delay(function(d,i) {
7890 return i * duration / data[0].values.length;
7891 });
7892 if (stacked){
7893 barSelection
7894 .attr('y', function(d,i,j) {
7895 var yVal = 0;
7896 // if stackable, stack it on top of the previous series
7897 if (!data[j].nonStackable) {
7898 yVal = y(d.y1);
7899 } else {
7900 if (getY(d,i) < 0){
7901 yVal = y(0);
7902 } else {
7903 if (y(0) - y(getY(d,i)) < -1){
7904 yVal = y(0) - 1;
7905 } else {
7906 yVal = y(getY(d, i)) || 0;
7910 return yVal;
7911 })
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);
7915 } else {
7916 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7918 })
7919 .attr('x', function(d,i,j) {
7920 var width = 0;
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);
7927 return width;
7928 })
7929 .attr('width', function(d,i,j){
7930 if (!data[j].nonStackable) {
7931 return x.rangeBand();
7932 } else {
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);
7940 return width;
7942 });
7944 else {
7945 barSelection
7946 .attr('x', function(d,i) {
7947 return d.series * x.rangeBand() / data.length;
7948 })
7949 .attr('width', x.rangeBand() / data.length)
7950 .attr('y', function(d,i) {
7951 return getY(d,i) < 0 ?
7952 y(0) :
7953 y(0) - y(getY(d,i)) < 1 ?
7954 y(0) - 1 :
7955 y(getY(d,i)) || 0;
7956 })
7957 .attr('height', function(d,i) {
7958 return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7959 });
7962 //store old scales for use in transitions on update
7963 x0 = x.copy();
7964 y0 = y.copy();
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;
7971 });
7973 renderWatch.renderEnd('multibar immediate');
7975 return chart;
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;
8013 }},
8014 duration: {get: function(){return duration;}, set: function(_){
8015 duration = _;
8016 renderWatch.reset(duration);
8017 }},
8018 color: {get: function(){return color;}, set: function(_){
8019 color = nv.utils.getColor(_);
8020 }},
8021 barColor: {get: function(){return barColor;}, set: function(_){
8022 barColor = _ ? nv.utils.getColor(_) : null;
8023 }}
8024 });
8026 nv.utils.initOptions(chart);
8028 return chart;
8029 };
8030 nv.models.multiBarChart = function() {
8031 "use strict";
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}
8046 , width = null
8047 , height = null
8048 , color = nv.utils.defaultColor()
8049 , showControls = true
8050 , controlLabels = {}
8051 , showLegend = true
8052 , showXAxis = true
8053 , showYAxis = true
8054 , rightAlignYAxis = false
8055 , reduceXTicks = true // if false a tick will show for every data point
8056 , staggerLabels = false
8057 , rotateLabels = 0
8058 , x //can be accessed via chart.xScale()
8059 , y //can be accessed via chart.yScale()
8060 , state = nv.utils.state()
8061 , defaultState = null
8062 , noData = null
8063 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
8064 , controlWidth = function() { return showControls ? 180 : 0 }
8065 , duration = 250
8068 state.stacked = false // DEPRECATED Maintained for backward compatibility
8070 multibar.stacked(false);
8071 xAxis
8072 .orient('bottom')
8073 .tickPadding(7)
8074 .showMaxMin(false)
8075 .tickFormat(function(d) { return d })
8077 yAxis
8078 .orient((rightAlignYAxis) ? 'right' : 'left')
8079 .tickFormat(d3.format(',.1f'))
8082 tooltip
8083 .duration(0)
8084 .valueFormatter(function(d, i) {
8085 return yAxis.tickFormat()(d, i);
8086 })
8087 .headerFormatter(function(d, i) {
8088 return xAxis.tickFormat()(d, i);
8089 });
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) {
8101 return function(){
8102 return {
8103 active: data.map(function(d) { return !d.disabled }),
8104 stacked: stacked
8105 };
8107 };
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];
8116 });
8118 };
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),
8128 that = 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() {
8134 if (duration === 0)
8135 container.call(chart);
8136 else
8137 container.transition()
8138 .duration(duration)
8139 .call(chart);
8140 };
8141 chart.container = this;
8143 state
8144 .setter(stateSetter(data), chart.update)
8145 .getter(stateGetter(data))
8146 .update();
8148 // DEPRECATED set state.disableddisabled
8149 state.disabled = data.map(function(d) { return !!d.disabled });
8151 if (!defaultState) {
8152 var key;
8153 defaultState = {};
8154 for (key in state) {
8155 if (state[key] instanceof Array)
8156 defaultState[key] = state[key].slice(0);
8157 else
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)
8165 return chart;
8166 } else {
8167 container.selectAll('.nv-noData').remove();
8170 // Setup Scales
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');
8185 // Legend
8186 if (showLegend) {
8187 legend.width(availableWidth - controlWidth());
8189 g.select('.nv-legendWrap')
8190 .datum(data)
8191 .call(legend);
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) +')');
8202 // Controls
8203 if (showControls) {
8204 var controlsData = [
8205 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8206 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
8207 ];
8209 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8210 g.select('.nv-controlsWrap')
8211 .datum(controlsData)
8212 .attr('transform', 'translate(0,' + (-margin.top) +')')
8213 .call(controls);
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)
8223 multibar
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);
8237 // Setup Axes
8238 if (showXAxis) {
8239 xAxis
8240 .scale(x)
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')
8247 .call(xAxis);
8249 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8251 xTicks
8252 .selectAll('line, text')
8253 .style('opacity', 1)
8255 if (staggerLabels) {
8256 var getTranslate = function(x,y) {
8257 return "translate(" + x + "," + y + ")";
8258 };
8260 var staggerUp = 5, staggerDown = 17; //pixels to stagger by
8261 // Issue #140
8262 xTicks
8263 .selectAll("text")
8264 .attr('transform', function(d,i,j) {
8265 return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8266 });
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);
8272 });
8275 if (reduceXTicks)
8276 xTicks
8277 .filter(function(d,i) {
8278 return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8279 })
8280 .selectAll('text, line')
8281 .style('opacity', 0);
8283 if(rotateLabels)
8284 xTicks
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);
8293 if (showYAxis) {
8294 yAxis
8295 .scale(y)
8296 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8297 .tickSize( -availableWidth, 0);
8299 g.select('.nv-y.nv-axis')
8300 .call(yAxis);
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);
8311 chart.update();
8312 });
8314 controls.dispatch.on('legendClick', function(d,i) {
8315 if (!d.disabled) return;
8316 controlsData = controlsData.map(function(s) {
8317 s.disabled = true;
8318 return s;
8319 });
8320 d.disabled = false;
8322 switch (d.key) {
8323 case 'Grouped':
8324 case controlLabels.grouped:
8325 multibar.stacked(false);
8326 break;
8327 case 'Stacked':
8328 case controlLabels.stacked:
8329 multibar.stacked(true);
8330 break;
8333 state.stacked = multibar.stacked();
8334 dispatch.stateChange(state);
8335 chart.update();
8336 });
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];
8343 });
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;
8351 chart.update();
8352 });
8353 });
8355 renderWatch.renderEnd('multibarchart immediate');
8356 return chart;
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);
8365 evt['series'] = {
8366 key: evt.data.key,
8367 value: chart.y()(evt.data),
8368 color: evt.color
8369 };
8370 tooltip.data(evt).hidden(false);
8371 });
8373 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
8374 tooltip.hidden(true);
8375 });
8377 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
8378 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
8379 });
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(!!_);
8417 }},
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(_);
8422 }},
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;
8430 }},
8431 duration: {get: function(){return duration;}, set: function(_){
8432 duration = _;
8433 multibar.duration(duration);
8434 xAxis.duration(duration);
8435 yAxis.duration(duration);
8436 renderWatch.reset(duration);
8437 }},
8438 color: {get: function(){return color;}, set: function(_){
8439 color = nv.utils.getColor(_);
8440 legend.color(color);
8441 }},
8442 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
8443 rightAlignYAxis = _;
8444 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
8445 }},
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();})
8449 }}
8450 });
8452 nv.utils.inheritOptions(chart, multibar);
8453 nv.utils.initOptions(chart);
8455 return chart;
8456 };
8458 nv.models.multiBarHorizontal = function() {
8459 "use strict";
8461 //============================================================
8462 // Public Variables with Default Settings
8463 //------------------------------------------------------------
8465 var margin = {top: 0, right: 0, bottom: 0, left: 0}
8466 , width = 960
8467 , height = 500
8468 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8469 , container = null
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
8479 , stacked = false
8480 , showValues = false
8481 , showBarLabels = false
8482 , valuePadding = 60
8483 , groupSpacing = 0.1
8484 , valueFormat = d3.format(',.2f')
8485 , delay = 1200
8486 , xDomain
8487 , yDomain
8488 , xRange
8489 , yRange
8490 , duration = 250
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);
8510 if (stacked)
8511 data = d3.layout.stack()
8512 .offset('zero')
8513 .values(function(d){ return d.values })
8514 .y(getY)
8515 (data);
8517 //add series index and key to each data point for reference
8518 data.forEach(function(series, i) {
8519 series.values.forEach(function(point) {
8520 point.series = i;
8521 point.key = series.key;
8522 });
8523 });
8525 // HACK for negative value stacking
8526 if (stacked)
8527 data[0].values.map(function(d,i) {
8528 var posBase = 0, negBase = 0;
8529 data.map(function(d) {
8530 var f = d.values[i]
8531 f.size = Math.abs(f.y);
8532 if (f.y<0) {
8533 f.y1 = negBase - f.size;
8534 negBase = negBase - f.size;
8535 } else
8537 f.y1 = posBase;
8538 posBase = posBase + f.size;
8540 });
8541 });
8543 // Setup Scales
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 }
8549 })
8550 });
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) ]);
8559 else
8560 y.range(yRange || [0, availableWidth]);
8562 x0 = x0 || x;
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)
8583 .remove();
8584 groups
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))) + ')'
8600 });
8602 barsEnter.append('rect')
8603 .attr('width', 0)
8604 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8606 bars
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({
8610 data: d,
8611 index: i,
8612 color: d3.select(this).style("fill")
8613 });
8614 })
8615 .on('mouseout', function(d,i) {
8616 d3.select(this).classed('hover', false);
8617 dispatch.elementMouseout({
8618 data: d,
8619 index: i,
8620 color: d3.select(this).style("fill")
8621 });
8622 })
8623 .on('mouseout', function(d,i) {
8624 dispatch.elementMouseout({
8625 data: d,
8626 index: i,
8627 color: d3.select(this).style("fill")
8628 });
8629 })
8630 .on('mousemove', function(d,i) {
8631 dispatch.elementMousemove({
8632 data: d,
8633 index: i,
8634 color: d3.select(this).style("fill")
8635 });
8636 })
8637 .on('click', function(d,i) {
8638 dispatch.elementClick({
8639 data: d,
8640 index: i,
8641 color: d3.select(this).style("fill")
8642 });
8643 d3.event.stopPropagation();
8644 })
8645 .on('dblclick', function(d,i) {
8646 dispatch.elementDblClick({
8647 data: d,
8648 index: i,
8649 color: d3.select(this).style("fill")
8650 });
8651 d3.event.stopPropagation();
8652 });
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(' ');
8666 })
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 + ')'
8670 });
8673 barsEnter.append('text');
8675 if (showValues && !stacked) {
8676 bars.select('text')
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)
8684 return t;
8685 if (!yerr.length)
8686 return t + '±' + valueFormat(Math.abs(yerr));
8687 return t + '+' + valueFormat(Math.abs(yerr[1])) + '-' + valueFormat(Math.abs(yerr[0]));
8688 });
8689 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8690 .select('text')
8691 .attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8692 } else {
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 });
8707 else {
8708 bars.selectAll('text.nv-bar-label').text('');
8711 bars
8712 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8714 if (barColor) {
8715 if (!disabled) disabled = data.map(function() { return true });
8716 bars
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(); });
8721 if (stacked)
8722 bars.watchTransition(renderWatch, 'multibarhorizontal: bars')
8723 .attr('transform', function(d,i) {
8724 return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8725 })
8726 .select('rect')
8727 .attr('width', function(d,i) {
8728 return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8729 })
8730 .attr('height', x.rangeBand() );
8731 else
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))
8737 + ',' +
8738 (d.series * x.rangeBand() / data.length
8740 x(getX(d,i)) )
8741 + ')'
8742 })
8743 .select('rect')
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)
8747 });
8749 //store old scales for use in transitions on update
8750 x0 = x.copy();
8751 y0 = y.copy();
8753 });
8755 renderWatch.renderEnd('multibarHorizontal immediate');
8756 return chart;
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;
8797 }},
8798 duration: {get: function(){return duration;}, set: function(_){
8799 duration = _;
8800 renderWatch.reset(duration);
8801 }},
8802 color: {get: function(){return color;}, set: function(_){
8803 color = nv.utils.getColor(_);
8804 }},
8805 barColor: {get: function(){return barColor;}, set: function(_){
8806 barColor = _ ? nv.utils.getColor(_) : null;
8807 }}
8808 });
8810 nv.utils.initOptions(chart);
8812 return chart;
8813 };
8815 nv.models.multiBarHorizontalChart = function() {
8816 "use strict";
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}
8831 , width = null
8832 , height = null
8833 , color = nv.utils.defaultColor()
8834 , showControls = true
8835 , controlLabels = {}
8836 , showLegend = true
8837 , showXAxis = true
8838 , showYAxis = true
8839 , stacked = false
8840 , x //can be accessed via chart.xScale()
8841 , y //can be accessed via chart.yScale()
8842 , state = nv.utils.state()
8843 , defaultState = null
8844 , noData = null
8845 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
8846 , controlWidth = function() { return showControls ? 180 : 0 }
8847 , duration = 250
8850 state.stacked = false; // DEPRECATED Maintained for backward compatibility
8852 multibar.stacked(stacked);
8854 xAxis
8855 .orient('left')
8856 .tickPadding(5)
8857 .showMaxMin(false)
8858 .tickFormat(function(d) { return d })
8860 yAxis
8861 .orient('bottom')
8862 .tickFormat(d3.format(',.1f'))
8865 tooltip
8866 .duration(0)
8867 .valueFormatter(function(d, i) {
8868 return yAxis.tickFormat()(d, i);
8869 })
8870 .headerFormatter(function(d, i) {
8871 return xAxis.tickFormat()(d, i);
8872 });
8874 controls.updateState(false);
8876 //============================================================
8877 // Private Variables
8878 //------------------------------------------------------------
8880 var stateGetter = function(data) {
8881 return function(){
8882 return {
8883 active: data.map(function(d) { return !d.disabled }),
8884 stacked: stacked
8885 };
8887 };
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];
8896 });
8898 };
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),
8910 that = 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();
8920 state
8921 .setter(stateSetter(data), chart.update)
8922 .getter(stateGetter(data))
8923 .update();
8925 // DEPRECATED set state.disableddisabled
8926 state.disabled = data.map(function(d) { return !!d.disabled });
8928 if (!defaultState) {
8929 var key;
8930 defaultState = {};
8931 for (key in state) {
8932 if (state[key] instanceof Array)
8933 defaultState[key] = state[key].slice(0);
8934 else
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)
8942 return chart;
8943 } else {
8944 container.selectAll('.nv-noData').remove();
8947 // Setup Scales
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')
8959 .append('line');
8960 gEnter.append('g').attr('class', 'nv-barsWrap');
8961 gEnter.append('g').attr('class', 'nv-legendWrap');
8962 gEnter.append('g').attr('class', 'nv-controlsWrap');
8964 // Legend
8965 if (showLegend) {
8966 legend.width(availableWidth - controlWidth());
8968 g.select('.nv-legendWrap')
8969 .datum(data)
8970 .call(legend);
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) +')');
8981 // Controls
8982 if (showControls) {
8983 var controlsData = [
8984 { key: controlLabels.grouped || 'Grouped', disabled: multibar.stacked() },
8985 { key: controlLabels.stacked || 'Stacked', disabled: !multibar.stacked() }
8986 ];
8988 controls.width(controlWidth()).color(['#444', '#444', '#444']);
8989 g.select('.nv-controlsWrap')
8990 .datum(controlsData)
8991 .attr('transform', 'translate(0,' + (-margin.top) +')')
8992 .call(controls);
8995 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8997 // Main Chart Component(s)
8998 multibar
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);
9011 // Setup Axes
9012 if (showXAxis) {
9013 xAxis
9014 .scale(x)
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');
9022 xTicks
9023 .selectAll('line, text');
9026 if (showYAxis) {
9027 yAxis
9028 .scale(y)
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);
9037 // Zero line
9038 g.select(".nv-zeroLine line")
9039 .attr("x1", y(0))
9040 .attr("x2", y(0))
9041 .attr("y1", 0)
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);
9053 chart.update();
9054 });
9056 controls.dispatch.on('legendClick', function(d,i) {
9057 if (!d.disabled) return;
9058 controlsData = controlsData.map(function(s) {
9059 s.disabled = true;
9060 return s;
9061 });
9062 d.disabled = false;
9064 switch (d.key) {
9065 case 'Grouped':
9066 multibar.stacked(false);
9067 break;
9068 case 'Stacked':
9069 multibar.stacked(true);
9070 break;
9073 state.stacked = multibar.stacked();
9074 dispatch.stateChange(state);
9075 stacked = multibar.stacked();
9077 chart.update();
9078 });
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];
9086 });
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;
9097 chart.update();
9098 });
9099 });
9100 renderWatch.renderEnd('multibar horizontal chart immediate');
9101 return chart;
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);
9110 evt['series'] = {
9111 key: evt.data.key,
9112 value: chart.y()(evt.data),
9113 color: evt.color
9114 };
9115 tooltip.data(evt).hidden(false);
9116 });
9118 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
9119 tooltip.hidden(true);
9120 });
9122 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
9123 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9124 });
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(!!_);
9159 }},
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(_);
9164 }},
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;
9172 }},
9173 duration: {get: function(){return duration;}, set: function(_){
9174 duration = _;
9175 renderWatch.reset(duration);
9176 multibar.duration(duration);
9177 xAxis.duration(duration);
9178 yAxis.duration(duration);
9179 }},
9180 color: {get: function(){return color;}, set: function(_){
9181 color = nv.utils.getColor(_);
9182 legend.color(color);
9183 }},
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();})
9187 }}
9188 });
9190 nv.utils.inheritOptions(chart, multibar);
9191 nv.utils.initOptions(chart);
9193 return chart;
9194 };
9195 nv.models.multiChart = function() {
9196 "use strict";
9198 //============================================================
9199 // Public Variables with Default Settings
9200 //------------------------------------------------------------
9202 var margin = {top: 30, right: 20, bottom: 50, left: 60},
9203 color = nv.utils.defaultColor(),
9204 width = null,
9205 height = null,
9206 showLegend = true,
9207 noData = null,
9208 yDomain1,
9209 yDomain2,
9210 getX = function(d) { return d.x },
9211 getY = function(d) { return d.y},
9212 interpolate = 'monotone',
9213 useVoronoi = true
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),
9244 that = 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);
9263 return chart;
9264 } else {
9265 container.selectAll('.nv-noData').remove();
9268 var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9269 .map(function(d) {
9270 return d.values.map(function(d,i) {
9271 return { x: d.x, y: d.y }
9272 })
9273 });
9275 var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9276 .map(function(d) {
9277 return d.values.map(function(d,i) {
9278 return { x: d.x, y: d.y }
9279 })
9280 });
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);
9303 });
9305 if (showLegend) {
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)');
9316 return series;
9317 }))
9318 .call(legend);
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) +')');
9329 lines1
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'}));
9334 lines2
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'}));
9339 bars1
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'}));
9343 bars2
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'}));
9347 stack1
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'}));
9351 stack2
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);}
9401 xAxis
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'))
9408 .call(xAxis);
9410 yAxis1
9411 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9412 .tickSize( -availableWidth, 0);
9415 d3.transition(g.select('.nv-y1.nv-axis'))
9416 .call(yAxis1);
9418 yAxis2
9419 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9420 .tickSize( -availableWidth, 0);
9422 d3.transition(g.select('.nv-y2.nv-axis'))
9423 .call(yAxis2);
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) {
9434 chart.update();
9435 });
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;
9444 evt.series = {
9445 value: evt.point.y,
9446 color: evt.point.color
9447 };
9448 tooltip
9449 .duration(100)
9450 .valueFormatter(function(d, i) {
9451 return yaxis.tickFormat()(d, i);
9452 })
9453 .data(evt)
9454 .position(evt.pos)
9455 .hidden(false);
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);
9462 tooltip
9463 .duration(100)
9464 .valueFormatter(function(d, i) {
9465 return yaxis.tickFormat()(d, i);
9466 })
9467 .data(evt)
9468 .position(evt.pos)
9469 .hidden(false);
9472 function mouseover_bar(evt) {
9473 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
9475 evt.value = bars1.x()(evt.data);
9476 evt['series'] = {
9477 value: bars1.y()(evt.data),
9478 color: evt.color
9479 };
9480 tooltip
9481 .duration(0)
9482 .valueFormatter(function(d, i) {
9483 return yaxis.tickFormat()(d, i);
9484 })
9485 .data(evt)
9486 .hidden(false);
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)
9493 });
9494 lines2.dispatch.on('elementMouseout.tooltip', function(evt) {
9495 tooltip.hidden(true)
9496 });
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)
9502 });
9503 stack2.dispatch.on('elementMouseout.tooltip', function(evt) {
9504 tooltip.hidden(true)
9505 });
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);
9512 });
9513 bars2.dispatch.on('elementMouseout.tooltip', function(evt) {
9514 tooltip.hidden(true);
9515 });
9516 bars1.dispatch.on('elementMousemove.tooltip', function(evt) {
9517 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9518 });
9519 bars2.dispatch.on('elementMousemove.tooltip', function(evt) {
9520 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9521 });
9523 });
9525 return chart;
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(!!_);
9561 }},
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(_);
9566 }},
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;
9574 }},
9575 color: {get: function(){return color;}, set: function(_){
9576 color = nv.utils.getColor(_);
9577 }},
9578 x: {get: function(){return getX;}, set: function(_){
9579 getX = _;
9580 lines1.x(_);
9581 lines2.x(_);
9582 bars1.x(_);
9583 bars2.x(_);
9584 stack1.x(_);
9585 stack2.x(_);
9586 }},
9587 y: {get: function(){return getY;}, set: function(_){
9588 getY = _;
9589 lines1.y(_);
9590 lines2.y(_);
9591 stack1.y(_);
9592 stack2.y(_);
9593 bars1.y(_);
9594 bars2.y(_);
9595 }},
9596 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
9597 useVoronoi=_;
9598 lines1.useVoronoi(_);
9599 lines2.useVoronoi(_);
9600 stack1.useVoronoi(_);
9601 stack2.useVoronoi(_);
9602 }}
9603 });
9605 nv.utils.initOptions(chart);
9607 return chart;
9608 };
9611 nv.models.ohlcBar = function() {
9612 "use strict";
9614 //============================================================
9615 // Public Variables with Default Settings
9616 //------------------------------------------------------------
9618 var margin = {top: 0, right: 0, bottom: 0, left: 0}
9619 , width = null
9620 , height = null
9621 , id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9622 , container = null
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 }
9631 , forceX = []
9632 , forceY = []
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
9634 , clipEdge = true
9635 , color = nv.utils.defaultColor()
9636 , interactive = false
9637 , xDomain
9638 , yDomain
9639 , xRange
9640 , yRange
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);
9656 // ohlc bar width.
9657 var w = (availableWidth / data[0].values.length) * .9;
9659 // Setup Scales
9660 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9662 if (padData)
9663 x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
9664 else
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])
9675 x.domain()[0] ?
9676 x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9677 : x.domain([-1,1]);
9679 if (y.domain()[0] === y.domain()[1])
9680 y.domain()[0] ?
9681 y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9682 : y.domain([-1,1]);
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 + ')');
9695 container
9696 .on('click', function(d,i) {
9697 dispatch.chartClick({
9698 data: d,
9699 index: i,
9700 pos: d3.event,
9701 id: id
9702 });
9703 });
9705 defsEnter.append('clipPath')
9706 .attr('id', 'nv-chart-clip-path-' + id)
9707 .append('rect');
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) {
9722 return 'm0,0l0,'
9723 + (y(getOpen(d,i))
9724 - y(getHigh(d,i)))
9725 + 'l'
9726 + (-w/2)
9727 + ',0l'
9728 + (w/2)
9729 + ',0l0,'
9730 + (y(getLow(d,i)) - y(getOpen(d,i)))
9731 + 'l0,'
9732 + (y(getClose(d,i))
9733 - y(getLow(d,i)))
9734 + 'l'
9735 + (w/2)
9736 + ',0l'
9737 + (-w/2)
9738 + ',0z';
9739 })
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]; })
9743 .attr('x', 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;
9750 });
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;
9756 return 'm0,0l0,'
9757 + (y(getOpen(d,i))
9758 - y(getHigh(d,i)))
9759 + 'l'
9760 + (-w/2)
9761 + ',0l'
9762 + (w/2)
9763 + ',0l0,'
9764 + (y(getLow(d,i))
9765 - y(getOpen(d,i)))
9766 + 'l0,'
9767 + (y(getClose(d,i))
9768 - y(getLow(d,i)))
9769 + 'l'
9770 + (w/2)
9771 + ',0l'
9772 + (-w/2)
9773 + ',0z';
9774 });
9775 });
9777 return chart;
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)
9787 };
9789 chart.clearHighlights = function() {
9790 container.select(".nv-ohlcBar .nv-tick.hover")
9791 .classed("hover", false)
9793 };
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;
9832 }},
9833 color: {get: function(){return color;}, set: function(_){
9834 color = nv.utils.getColor(_);
9835 }}
9836 });
9838 nv.utils.initOptions(chart);
9839 return chart;
9840 };
9841 // Code adapted from Jason Davies' "Parallel Coordinates"
9842 // http://bl.ocks.org/jasondavies/1341281
9843 nv.models.parallelCoordinates = function() {
9844 "use strict";
9846 //============================================================
9847 // Public Variables with Default Settings
9848 //------------------------------------------------------------
9850 var margin = {top: 30, right: 0, bottom: 10, left: 0}
9851 , width = null
9852 , height = null
9853 , x = d3.scale.ordinal()
9854 , y = {}
9855 , dimensionNames = []
9856 , dimensionFormats = []
9857 , color = nv.utils.defaultColor()
9858 , filters = []
9859 , active = []
9860 , dragging = []
9861 , lineTension = 1
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
9879 // Setup Scales
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;
9891 extent[0] = 0;
9892 extent[1] = 0;
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()
9902 .domain(extent)
9903 .range([(availableHeight - 12) * 0.9, 0]);
9905 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
9907 return d != 'name';
9908 });
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({
9966 label: d.name,
9967 data: d.data,
9968 index: i,
9969 pos: [d3.mouse(this.parentNode)[0], d3.mouse(this.parentNode)[1]]
9970 });
9972 });
9973 foreground.on("mouseout", function (d, i) {
9974 d3.select(this).classed('hover', false);
9975 dispatch.elementMouseout({
9976 label: d.name,
9977 data: d.data,
9978 index: i
9979 });
9980 });
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")
9995 .attr('dy', '-1em')
9996 .attr('text-anchor', 'middle')
9997 .text(String)
9998 .on("mouseover", function(d, i) {
9999 dispatch.elementMouseover({
10000 dim: d,
10001 pos: [d3.mouse(this.parentNode.parentNode)[0], d3.mouse(this.parentNode.parentNode)[1]]
10002 });
10003 })
10004 .on("mouseout", function(d, i) {
10005 dispatch.elementMouseout({
10006 dim: d
10007 });
10008 })
10009 .call(axisDrag);
10011 dimensions.select('.nv-axis')
10012 .each(function (d, i) {
10013 d3.select(this).call(axis.scale(y[d]).tickFormat(d3.format(dimensionFormats[i])));
10014 });
10016 dimensions.select('.nv-parallelCoordinates-brush')
10017 .each(function (d) {
10018 d3.select(this).call(y[d].brush);
10019 })
10020 .selectAll('rect')
10021 .attr('x', -8)
10022 .attr('width', 16);
10024 // Returns the path for a given data point.
10025 function path(d) {
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");
10048 } else {
10049 missingValuesline.style("display", "none");
10050 missingValueslineText.style("display", "none");
10053 return [x(p), y[p](d[p])];
10054 }));
10057 // Handles a brush event, toggling the display of foreground lines.
10058 function brush() {
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) {
10064 filters[i] = {
10065 dimension: d,
10066 extent: extents[i]
10068 });
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];
10075 });
10076 if (isActive) active.push(d);
10077 return isActive ? null : 'none';
10078 });
10080 dispatch.brush({
10081 filters: filters,
10082 active: active
10083 });
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) + ")");
10104 foreground
10105 .attr("d", path);
10106 background
10107 .attr("d", path)
10108 .attr("visibility", null);
10112 function position(d) {
10113 var v = dragging[d];
10114 return v == null ? x(d) : v;
10116 });
10118 return chart;
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 = _;
10141 }},
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;
10149 }},
10150 color: {get: function(){return color;}, set: function(_){
10151 color = nv.utils.getColor(_);
10152 }}
10153 });
10155 nv.utils.initOptions(chart);
10156 return chart;
10157 };
10158 nv.models.pie = function() {
10159 "use strict";
10161 //============================================================
10162 // Public Variables with Default Settings
10163 //------------------------------------------------------------
10165 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10166 , width = 500
10167 , height = 500
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
10171 , container = null
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
10178 , donut = false
10179 , title = false
10180 , growOnHover = true
10181 , titleOffset = 0
10182 , labelSunbeamLayout = false
10183 , startAngle = false
10184 , padAngle = false
10185 , endAngle = false
10186 , cornerRadius = 0
10187 , donutRatio = 0.5
10188 , arcsRadius = []
10189 , dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'elementMousemove', 'renderEnd')
10192 var arcs = [];
10193 var arcsOver = [];
10195 //============================================================
10196 // chart function
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);
10219 } else {
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 + ')');
10238 //
10239 container.on('click', function(d,i) {
10240 dispatch.chartClick({
10241 data: d,
10242 index: i,
10243 pos: d3.event,
10244 id: id
10245 });
10246 });
10248 arcs = [];
10249 arcsOver = [];
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);
10263 if (donut) {
10264 arc.innerRadius(arcsRadiusInner[i]);
10265 arcOver.innerRadius(arcsRadiusInner[i]);
10268 if (arc.cornerRadius && cornerRadius) {
10269 arc.cornerRadius(cornerRadius);
10270 arcOver.cornerRadius(cornerRadius);
10273 arcs.push(arc);
10274 arcsOver.push(arcOver);
10277 // Setup the Pie chart and choose the data element
10278 var pie = d3.layout.pie()
10279 .sort(null)
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) {
10294 return title;
10295 })
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 + ')';
10300 });
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);
10313 if (growOnHover) {
10314 d3.select(this).select("path").transition()
10315 .duration(70)
10316 .attr("d", arcsOver[i]);
10318 dispatch.elementMouseover({
10319 data: d.data,
10320 index: i,
10321 color: d3.select(this).style("fill")
10322 });
10323 });
10324 ae.on('mouseout', function(d, i) {
10325 d3.select(this).classed('hover', false);
10326 if (growOnHover) {
10327 d3.select(this).select("path").transition()
10328 .duration(50)
10329 .attr("d", arcs[i]);
10331 dispatch.elementMouseout({data: d.data, index: i});
10332 });
10333 ae.on('mousemove', function(d, i) {
10334 dispatch.elementMousemove({data: d.data, index: i});
10335 });
10336 ae.on('click', function(d, i) {
10337 dispatch.elementClick({
10338 data: d.data,
10339 index: i,
10340 color: d3.select(this).style("fill")
10341 });
10342 });
10343 ae.on('dblclick', function(d, i) {
10344 dispatch.elementDblClick({
10345 data: d.data,
10346 index: i,
10347 color: d3.select(this).style("fill")
10348 });
10349 });
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) {
10355 this._current = d;
10356 });
10358 slices.select('path')
10359 .transition()
10360 .attr('d', function (d, i) { return arcs[i](d); })
10361 .attrTween('d', arcTween);
10363 if (showLabels) {
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) {
10370 if (donut) {
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) {
10389 rotateAngle -= 90;
10390 } else {
10391 rotateAngle += 90;
10393 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
10394 } else {
10395 d.outerRadius = radius + 10; // Set Outer Coordinate
10396 d.innerRadius = radius + 15; // Set Inner Coordinate
10397 return 'translate(' + labelsArc[i].centroid(d) + ')'
10399 });
10401 group.append('rect')
10402 .style('stroke', '#fff')
10403 .style('fill', '#fff')
10404 .attr("rx", 3)
10405 .attr("ry", 3);
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')
10410 });
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;
10417 };
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) {
10425 rotateAngle -= 90;
10426 } else {
10427 rotateAngle += 90;
10429 return 'translate(' + labelsArc[i].centroid(d) + ') rotate(' + rotateAngle + ')';
10430 } else {
10431 d.outerRadius = radius + 10; // Set Outer Coordinate
10432 d.innerRadius = radius + 15; // Set Inner Coordinate
10434 /*
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.
10438 */
10439 var center = labelsArc[i].centroid(d);
10440 if (d.value) {
10441 var hashKey = createHashKey(center);
10442 if (labelLocationHash[hashKey]) {
10443 center[1] -= avgHeight;
10445 labelLocationHash[createHashKey(center)] = true;
10447 return 'translate(' + center + ')'
10449 });
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';
10455 })
10456 .text(function(d, i) {
10457 var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10458 var label = '';
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)
10466 });
10467 } else {
10468 switch (labelType) {
10469 case 'key':
10470 label = getX(d.data);
10471 break;
10472 case 'value':
10473 label = valueFormat(getY(d.data));
10474 break;
10475 case 'percent':
10476 label = d3.format('%')(percent);
10477 break;
10480 return label;
10481 })
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));
10500 };
10502 });
10504 renderWatch.renderEnd('pie immediate');
10505 return chart;
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(_){
10539 labelsOutside=_;
10540 nv.deprecated('pieLabelsOutside', 'use labelsOutside instead');
10541 }},
10542 // depreciated after 1.7.1
10543 donutLabelsOutside: {get: function(){return labelsOutside;}, set: function(_){
10544 labelsOutside=_;
10545 nv.deprecated('donutLabelsOutside', 'use labelsOutside instead');
10546 }},
10547 // deprecated after 1.7.1
10548 labelFormat: {get: function(){ return valueFormat;}, set: function(_) {
10549 valueFormat=_;
10550 nv.deprecated('labelFormat','use valueFormat instead');
10551 }},
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;
10559 }},
10560 y: {get: function(){return getY;}, set: function(_){
10561 getY=d3.functor(_);
10562 }},
10563 color: {get: function(){return color;}, set: function(_){
10564 color=nv.utils.getColor(_);
10565 }},
10566 labelType: {get: function(){return labelType;}, set: function(_){
10567 labelType= _ || 'key';
10568 }}
10569 });
10571 nv.utils.initOptions(chart);
10572 return chart;
10573 };
10574 nv.models.pieChart = function() {
10575 "use strict";
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}
10586 , width = null
10587 , height = null
10588 , showLegend = true
10589 , legendPosition = "top"
10590 , color = nv.utils.defaultColor()
10591 , state = nv.utils.state()
10592 , defaultState = null
10593 , noData = null
10594 , duration = 250
10595 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState','renderEnd')
10598 tooltip
10599 .headerEnabled(false)
10600 .duration(0)
10601 .valueFormatter(function(d, i) {
10602 return pie.valueFormat()(d, i);
10603 });
10605 //============================================================
10606 // Private Variables
10607 //------------------------------------------------------------
10609 var renderWatch = nv.utils.renderWatch(dispatch);
10611 var stateGetter = function(data) {
10612 return function(){
10613 return {
10614 active: data.map(function(d) { return !d.disabled })
10615 };
10617 };
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];
10624 });
10627 };
10629 //============================================================
10630 // Chart function
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);
10641 var that = this;
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))
10650 .update();
10652 //set state.disabled
10653 state.disabled = data.map(function(d) { return !!d.disabled });
10655 if (!defaultState) {
10656 var key;
10657 defaultState = {};
10658 for (key in state) {
10659 if (state[key] instanceof Array)
10660 defaultState[key] = state[key].slice(0);
10661 else
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);
10669 return chart;
10670 } else {
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');
10682 // Legend
10683 if (showLegend) {
10684 if (legendPosition === "top") {
10685 legend.width( availableWidth ).key(pie.x());
10687 wrap.select('.nv-legendWrap')
10688 .datum(data)
10689 .call(legend);
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')
10708 .datum(data)
10709 .call(legend)
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);
10729 chart.update();
10730 });
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];
10737 });
10738 state.disabled = e.disabled;
10740 chart.update();
10741 });
10742 });
10744 renderWatch.renderEnd('pieChart immediate');
10745 return chart;
10748 //============================================================
10749 // Event Handling/Dispatching (out of chart's scope)
10750 //------------------------------------------------------------
10752 pie.dispatch.on('elementMouseover.tooltip', function(evt) {
10753 evt['series'] = {
10754 key: chart.x()(evt.data),
10755 value: chart.y()(evt.data),
10756 color: evt.color
10757 };
10758 tooltip.data(evt).hidden(false);
10759 });
10761 pie.dispatch.on('elementMouseout.tooltip', function(evt) {
10762 tooltip.hidden(true);
10763 });
10765 pie.dispatch.on('elementMousemove.tooltip', function(evt) {
10766 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
10767 });
10769 //============================================================
10770 // Expose Public Variables
10771 //------------------------------------------------------------
10773 // expose chart's sub-components
10774 chart.legend = legend;
10775 chart.dispatch = dispatch;
10776 chart.pie = pie;
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(!!_);
10793 }},
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(_);
10798 }},
10800 // options that require extra logic in the setter
10801 color: {get: function(){return color;}, set: function(_){
10802 color = _;
10803 legend.color(color);
10804 pie.color(color);
10805 }},
10806 duration: {get: function(){return duration;}, set: function(_){
10807 duration = _;
10808 renderWatch.reset(duration);
10809 }},
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;
10815 }}
10816 });
10817 nv.utils.inheritOptions(chart, pie);
10818 nv.utils.initOptions(chart);
10819 return chart;
10820 };
10822 nv.models.scatter = function() {
10823 "use strict";
10825 //============================================================
10826 // Public Variables with Default Settings
10827 //------------------------------------------------------------
10829 var margin = {top: 0, right: 0, bottom: 0, left: 0}
10830 , width = null
10831 , height = null
10832 , color = nv.utils.defaultColor() // chooses color
10833 , id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10834 , container = null
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
10858 , sizeRange = null
10859 , singlePoint = false
10860 , dispatch = d3.dispatch('elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout', 'renderEnd')
10861 , useVoronoi = true
10862 , duration = 250
10866 //============================================================
10867 // Private Variables
10868 //------------------------------------------------------------
10870 var x0, y0, z0 // used to store previous scales
10871 , timeoutID
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) {
10889 point.series = i;
10890 });
10891 });
10893 // Setup Scales
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
10896 d3.merge(
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) }
10900 })
10901 })
10902 );
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 ]);
10909 else
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])
10922 x.domain()[0] ?
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])
10927 y.domain()[0] ?
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])) {
10932 x.domain([-1,1]);
10935 if ( isNaN(y.domain()[0])) {
10936 y.domain([-1,1]);
10939 x0 = x0 || x;
10940 y0 = y0 || y;
10941 z0 = z0 || z;
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)
10959 .append('rect');
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.
10982 */
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,
10988 groupIndex,
10989 pointIndex, point]; //temp hack to add noise until I think of a better way so there are no duplicates
10990 })
10991 .filter(function(pointArray, pointIndex) {
10992 return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10993 })
10994 })
10995 );
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([
11009 [-10,-10],
11010 [-10,height + 10],
11011 [width + 10,height + 10],
11012 [width + 10,-10]
11013 ]);
11015 var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11016 return {
11017 'data': bounds.clip(d),
11018 'series': vertices[i][2],
11019 'point': vertices[i][3]
11021 });
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)
11030 return 'M 0 0';
11031 else
11032 return "M" + d.data.join(",") + "Z";
11033 })
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
11040 if (showVoronoi) {
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));
11047 if (clipVoronoi) {
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")
11052 .data(vertices)
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;
11077 var pos = {
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
11080 };
11082 mDispatch({
11083 point: point,
11084 series: series,
11085 pos: pos,
11086 seriesIndex: d.series,
11087 pointIndex: d.point
11088 });
11089 };
11091 pointPaths
11092 .on('click', function(d) {
11093 mouseEventCallback(d, dispatch.elementClick);
11094 })
11095 .on('dblclick', function(d) {
11096 mouseEventCallback(d, dispatch.elementDblClick);
11097 })
11098 .on('mouseover', function(d) {
11099 mouseEventCallback(d, dispatch.elementMouseover);
11100 })
11101 .on('mouseout', function(d, i) {
11102 mouseEventCallback(d, dispatch.elementMouseout);
11103 });
11105 } else {
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({
11118 point: point,
11119 series: series,
11120 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11121 seriesIndex: d.series,
11122 pointIndex: i
11123 });
11124 })
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({
11131 point: point,
11132 series: series,
11133 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11134 seriesIndex: d.series,
11135 pointIndex: i
11136 });
11137 })
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({
11144 point: point,
11145 series: series,
11146 pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11147 seriesIndex: d.series,
11148 pointIndex: i,
11149 color: color(d, i)
11150 });
11151 })
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({
11158 point: point,
11159 series: series,
11160 seriesIndex: d.series,
11161 pointIndex: i,
11162 color: color(d, i)
11163 });
11164 });
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);
11174 groups.exit()
11175 .remove();
11176 groups
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]
11191 }).filter(
11192 function(pointArray, pointIndex) {
11193 return pointActive(pointArray[0], pointIndex)
11194 })
11195 });
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])) + ')'
11201 })
11202 .attr('d',
11203 nv.utils.symbol()
11204 .type(function(d) { return getShape(d[0]); })
11205 .size(function(d) { return z(getSize(d[0],d[1])) })
11206 );
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])) + ')'
11212 })
11213 .remove();
11214 points.each(function(d) {
11215 d3.select(this)
11216 .classed('nv-point', true)
11217 .classed('nv-point-' + d[1], true)
11218 .classed('nv-noninteractive', !interactive)
11219 .classed('hover',false)
11221 });
11222 points
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])) + ')'
11227 })
11228 .attr('d',
11229 nv.utils.symbol()
11230 .type(function(d) { return getShape(d[0]); })
11231 .size(function(d) { return z(getSize(d[0],d[1])) })
11232 );
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
11240 x0 = x.copy();
11241 y0 = y.copy();
11242 z0 = z.copy();
11244 });
11245 renderWatch.renderEnd('scatter immediate');
11246 return chart;
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);
11261 });
11262 return null;
11263 };
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);
11268 });
11269 };
11270 };
11272 // trigger calls from events too
11273 dispatch.on('elementMouseover.point', function(d) {
11274 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,true);
11275 });
11277 dispatch.on('elementMouseout.point', function(d) {
11278 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
11279 });
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;
11320 }},
11321 duration: {get: function(){return duration;}, set: function(_){
11322 duration = _;
11323 renderWatch.reset(duration);
11324 }},
11325 color: {get: function(){return color;}, set: function(_){
11326 color = nv.utils.getColor(_);
11327 }},
11328 useVoronoi: {get: function(){return useVoronoi;}, set: function(_){
11329 useVoronoi = _;
11330 if (useVoronoi === false) {
11331 clipVoronoi = false;
11333 }}
11334 });
11336 nv.utils.initOptions(chart);
11337 return chart;
11338 };
11340 nv.models.scatterChart = function() {
11341 "use strict";
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}
11357 , width = null
11358 , height = null
11359 , container = null
11360 , color = nv.utils.defaultColor()
11361 , x = scatter.xScale()
11362 , y = scatter.yScale()
11363 , showDistX = false
11364 , showDistY = false
11365 , showLegend = true
11366 , showXAxis = true
11367 , showYAxis = true
11368 , rightAlignYAxis = false
11369 , state = nv.utils.state()
11370 , defaultState = null
11371 , dispatch = d3.dispatch('stateChange', 'changeState', 'renderEnd')
11372 , noData = null
11373 , duration = 250
11376 scatter.xScale(x).yScale(y);
11377 xAxis.orient('bottom').tickPadding(10);
11378 yAxis
11379 .orient((rightAlignYAxis) ? 'right' : 'left')
11380 .tickPadding(10)
11382 distX.axis('x');
11383 distY.axis('y');
11384 tooltip
11385 .headerFormatter(function(d, i) {
11386 return xAxis.tickFormat()(d, i);
11387 })
11388 .valueFormatter(function(d, i) {
11389 return yAxis.tickFormat()(d, i);
11390 });
11392 //============================================================
11393 // Private Variables
11394 //------------------------------------------------------------
11396 var x0, y0
11397 , renderWatch = nv.utils.renderWatch(dispatch, duration);
11399 var stateGetter = function(data) {
11400 return function(){
11401 return {
11402 active: data.map(function(d) { return !d.disabled })
11403 };
11405 };
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];
11412 });
11414 };
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) {
11425 var that = this;
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);
11436 else
11437 container.transition().duration(duration).call(chart);
11438 };
11439 chart.container = this;
11441 state
11442 .setter(stateSetter(data), chart.update)
11443 .getter(stateGetter(data))
11444 .update();
11446 // DEPRECATED set state.disableddisabled
11447 state.disabled = data.map(function(d) { return !!d.disabled });
11449 if (!defaultState) {
11450 var key;
11451 defaultState = {};
11452 for (key in state) {
11453 if (state[key] instanceof Array)
11454 defaultState[key] = state[key].slice(0);
11455 else
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');
11464 return chart;
11465 } else {
11466 container.selectAll('.nv-noData').remove();
11469 // Setup Scales
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)");
11494 // Legend
11495 if (showLegend) {
11496 var legendWidth = availableWidth;
11497 legend.width(legendWidth);
11499 wrap.select('.nv-legendWrap')
11500 .datum(data)
11501 .call(legend);
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)
11515 scatter
11516 .width(availableWidth)
11517 .height(availableHeight)
11518 .color(data.map(function(d,i) {
11519 d.color = d.color || color(d, i);
11520 return d.color;
11521 }).filter(function(d,i) { return !data[i].disabled }));
11523 wrap.select('.nv-scatterWrap')
11524 .datum(data.filter(function(d) { return !d.disabled }))
11525 .call(scatter);
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) {
11533 return d;
11534 });
11536 regWrap.enter().append('g').attr('class', 'nv-regLines');
11538 var regLine = regWrap.selectAll('.nv-regLine')
11539 .data(function (d) {
11540 return [d]
11541 });
11543 regLine.enter()
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;
11550 })
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)
11556 })
11557 .attr('y2', function (d, i) {
11558 return y(x.domain()[1] * d.slope + d.intercept)
11559 })
11560 .style('stroke', function (d, i, j) {
11561 return color(d, j)
11562 })
11563 .style('stroke-opacity', function (d, i) {
11564 return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
11565 });
11567 // Setup Axes
11568 if (showXAxis) {
11569 xAxis
11570 .scale(x)
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] + ')')
11576 .call(xAxis);
11579 if (showYAxis) {
11580 yAxis
11581 .scale(y)
11582 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
11583 .tickSize( -availableWidth, 0);
11585 g.select('.nv-y.nv-axis')
11586 .call(yAxis);
11590 if (showDistX) {
11591 distX
11592 .getData(scatter.x())
11593 .scale(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 }))
11603 .call(distX);
11606 if (showDistY) {
11607 distY
11608 .getData(scatter.y())
11609 .scale(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 }))
11619 .call(distY);
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);
11630 chart.update();
11631 });
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];
11638 });
11639 state.disabled = e.disabled;
11641 chart.update();
11642 });
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)
11648 .attr('y1', 0);
11649 container.select('.nv-chart-' + scatter.id() + ' .nv-series-' + evt.seriesIndex + ' .nv-disty-' + evt.pointIndex)
11650 .attr('x2', distY.size());
11651 });
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);
11659 });
11661 //store old scales for use in transitions on update
11662 x0 = x.copy();
11663 y0 = y.copy();
11665 });
11667 renderWatch.renderEnd('scatter with line immediate');
11668 return chart;
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(!!_);
11705 }},
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(_);
11710 }},
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.');
11714 }},
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.');
11718 }},
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;
11726 }},
11727 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
11728 rightAlignYAxis = _;
11729 yAxis.orient( (_) ? 'right' : 'left');
11730 }},
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);
11736 }}
11737 });
11739 nv.utils.inheritOptions(chart, scatter);
11740 nv.utils.initOptions(chart);
11741 return chart;
11742 };
11744 nv.models.sparkline = function() {
11745 "use strict";
11747 //============================================================
11748 // Public Variables with Default Settings
11749 //------------------------------------------------------------
11751 var margin = {top: 2, right: 0, bottom: 2, left: 0}
11752 , width = 400
11753 , height = 32
11754 , container = null
11755 , animate = true
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'])
11761 , xDomain
11762 , yDomain
11763 , xRange
11764 , yRange
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);
11775 // Setup Scales
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();
11794 paths
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)) })
11799 );
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) {
11806 if (index != -1) {
11807 var result = data[index];
11808 result.pointIndex = index;
11809 return result;
11810 } else {
11811 return null;
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;});
11818 });
11819 points.enter().append('circle');
11820 points.exit().remove();
11821 points
11822 .attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
11823 .attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
11824 .attr('r', 2)
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'
11828 });
11829 });
11831 return chart;
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=_;}},
11852 //functor options
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;
11862 }},
11863 color: {get: function(){return color;}, set: function(_){
11864 color = nv.utils.getColor(_);
11865 }}
11866 });
11868 nv.utils.initOptions(chart);
11869 return chart;
11870 };
11872 nv.models.sparklinePlus = function() {
11873 "use strict";
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}
11882 , width = null
11883 , height = null
11884 , x
11885 , y
11886 , index = []
11887 , paused = false
11888 , xTickFormat = d3.format(',r')
11889 , yTickFormat = d3.format(',.2f')
11890 , showLastValue = true
11891 , alignValue = true
11892 , rightAlignValue = false
11893 , noData = null
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)
11910 return chart;
11911 } else {
11912 container.selectAll('.nv-noData').remove();
11915 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
11917 // Setup Scales
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');
11949 value
11950 .attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
11951 .attr('y', alignValue ? function (d) {
11952 return y(d)
11953 } : 0)
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);
11979 hoverValue.exit()
11980 .transition().duration(250)
11981 .style('stroke-opacity', 0)
11982 .style('fill-opacity', 0)
11983 .remove();
11985 hoverValue
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')
11994 .attr('x1', 0)
11995 .attr('y1', -margin.top)
11996 .attr('x2', 0)
11997 .attr('y2', availableHeight);
11999 hoverEnter.append('text').attr('class', 'nv-xValue')
12000 .attr('x', -6)
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')
12009 .attr('x', 6)
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);
12029 closestIndex = i;
12032 return closestIndex;
12035 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
12036 updateValueLine();
12039 });
12041 return chart;
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;
12070 }}
12071 });
12073 nv.utils.inheritOptions(chart, sparkline);
12074 nv.utils.initOptions(chart);
12076 return chart;
12077 };
12079 nv.models.stackedArea = function() {
12080 "use strict";
12082 //============================================================
12083 // Public Variables with Default Settings
12084 //------------------------------------------------------------
12086 var margin = {top: 0, right: 0, bottom: 0, left: 0}
12087 , width = 960
12088 , height = 500
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
12091 , container = null
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
12094 , style = 'stack'
12095 , offset = 'zero'
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()
12102 , duration = 250
12103 , dispatch = d3.dispatch('areaClick', 'areaMouseover', 'areaMouseout','renderEnd', 'elementClick', 'elementMouseover', 'elementMouseout')
12106 scatter
12107 .pointSize(2.2) // default size
12108 .pointDomain([2.2, 2.2]) // all the same size by default
12111 /************************************
12112 * offset:
12113 * 'wiggle' (stream)
12114 * 'zero' (stacked)
12115 * 'expand' (normalize to 100%)
12116 * 'silhouette' (simple centered)
12118 * order:
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);
12135 // Setup Scales
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) {
12144 d.index = j;
12145 d.seriesIndex = i;
12146 return d;
12147 });
12148 });
12150 var dataFiltered = data.filter(function(series) {
12151 return !series.disabled;
12152 });
12154 data = d3.layout.stack()
12155 .order(order)
12156 .offset(offset)
12157 .values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
12158 .x(getX)
12159 .y(getY)
12160 .out(function(d, y0, y) {
12161 d.display = {
12162 y: y,
12163 y0: y0
12164 };
12165 })
12166 (dataFiltered);
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);
12186 scatter
12187 .width(availableWidth)
12188 .height(availableHeight)
12189 .x(getX)
12190 .y(function(d) { return d.display.y + d.display.y0 })
12191 .forceY([0])
12192 .color(data.map(function(d,i) {
12193 return d.color || color(d, d.seriesIndex);
12194 }));
12196 var scatterWrap = g.select('.nv-scatterWrap')
12197 .datum(data);
12199 scatterWrap.call(scatter);
12201 defsEnter.append('clipPath')
12202 .attr('id', 'nv-edge-clip-' + id)
12203 .append('rect');
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)) })
12213 .y0(function(d) {
12214 return y(d.display.y0)
12215 })
12216 .y1(function(d) {
12217 return y(d.display.y + d.display.y0)
12218 })
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);
12232 })
12233 .on('mouseover', function(d,i) {
12234 d3.select(this).classed('hover', true);
12235 dispatch.areaMouseover({
12236 point: d,
12237 series: d.key,
12238 pos: [d3.event.pageX, d3.event.pageY],
12239 seriesIndex: d.seriesIndex
12240 });
12241 })
12242 .on('mouseout', function(d,i) {
12243 d3.select(this).classed('hover', false);
12244 dispatch.areaMouseout({
12245 point: d,
12246 series: d.key,
12247 pos: [d3.event.pageX, d3.event.pageY],
12248 seriesIndex: d.seriesIndex
12249 });
12250 })
12251 .on('click', function(d,i) {
12252 d3.select(this).classed('hover', false);
12253 dispatch.areaClick({
12254 point: d,
12255 series: d.key,
12256 pos: [d3.event.pageX, d3.event.pageY],
12257 seriesIndex: d.seriesIndex
12258 });
12259 });
12261 path.exit().remove();
12262 path.style('fill', function(d,i){
12263 return d.color || color(d, d.seriesIndex)
12264 })
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)
12269 });
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);
12277 });
12278 scatter.dispatch.on('elementMouseout.area', function(e) {
12279 g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
12280 });
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
12286 i,
12287 j,
12288 o,
12289 y0 = [];
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;
12305 return y0;
12306 };
12308 });
12310 renderWatch.renderEnd('stackedArea immediate');
12311 return chart;
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;
12327 interpolate = _;
12328 return chart;
12329 };
12331 chart.duration = function(_) {
12332 if (!arguments.length) return duration;
12333 duration = _;
12334 renderWatch.reset(duration);
12335 scatter.duration(duration);
12336 return chart;
12337 };
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;
12362 }},
12363 color: {get: function(){return color;}, set: function(_){
12364 color = nv.utils.getColor(_);
12365 }},
12366 style: {get: function(){return style;}, set: function(_){
12367 style = _;
12368 switch (style) {
12369 case 'stack':
12370 chart.offset('zero');
12371 chart.order('default');
12372 break;
12373 case 'stream':
12374 chart.offset('wiggle');
12375 chart.order('inside-out');
12376 break;
12377 case 'stream-center':
12378 chart.offset('silhouette');
12379 chart.order('inside-out');
12380 break;
12381 case 'expand':
12382 chart.offset('expand');
12383 chart.order('default');
12384 break;
12385 case 'stack_percent':
12386 chart.offset(chart.d3_stackedOffset_stackPercent);
12387 chart.order('default');
12388 break;
12390 }},
12391 duration: {get: function(){return duration;}, set: function(_){
12392 duration = _;
12393 renderWatch.reset(duration);
12394 scatter.duration(duration);
12395 }}
12396 });
12398 nv.utils.inheritOptions(chart, scatter);
12399 nv.utils.initOptions(chart);
12401 return chart;
12402 };
12404 nv.models.stackedAreaChart = function() {
12405 "use strict";
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}
12421 , width = null
12422 , height = null
12423 , color = nv.utils.defaultColor()
12424 , showControls = true
12425 , showLegend = true
12426 , showXAxis = true
12427 , showYAxis = 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
12434 , noData = null
12435 , dispatch = d3.dispatch('stateChange', 'changeState','renderEnd')
12436 , controlWidth = 250
12437 , controlOptions = ['Stacked','Stream','Expanded']
12438 , controlLabels = {}
12439 , duration = 250
12442 state.style = stacked.style();
12443 xAxis.orient('bottom').tickPadding(7);
12444 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
12446 tooltip
12447 .headerFormatter(function(d, i) {
12448 return xAxis.tickFormat()(d, i);
12449 })
12450 .valueFormatter(function(d, i) {
12451 return yAxis.tickFormat()(d, i);
12452 });
12454 interactiveLayer.tooltip
12455 .headerFormatter(function(d, i) {
12456 return xAxis.tickFormat()(d, i);
12457 })
12458 .valueFormatter(function(d, i) {
12459 return yAxis.tickFormat()(d, i);
12460 });
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) {
12475 return function(){
12476 return {
12477 active: data.map(function(d) { return !d.disabled }),
12478 style: stacked.style()
12479 };
12481 };
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];
12490 });
12492 };
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),
12504 that = 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;
12513 state
12514 .setter(stateSetter(data), chart.update)
12515 .getter(stateGetter(data))
12516 .update();
12518 // DEPRECATED set state.disabled
12519 state.disabled = data.map(function(d) { return !!d.disabled });
12521 if (!defaultState) {
12522 var key;
12523 defaultState = {};
12524 for (key in state) {
12525 if (state[key] instanceof Array)
12526 defaultState[key] = state[key].slice(0);
12527 else
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)
12535 return chart;
12536 } else {
12537 container.selectAll('.nv-noData').remove();
12540 // Setup Scales
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);
12559 // Legend
12560 if (showLegend) {
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) +')');
12575 // Controls
12576 if (showControls) {
12577 var controlsData = [
12579 key: controlLabels.stacked || 'Stacked',
12580 metaKey: 'Stacked',
12581 disabled: stacked.style() != 'stack',
12582 style: 'stack'
12583 },
12585 key: controlLabels.stream || 'Stream',
12586 metaKey: 'Stream',
12587 disabled: stacked.style() != 'stream',
12588 style: 'stream'
12589 },
12591 key: controlLabels.expanded || 'Expanded',
12592 metaKey: 'Expanded',
12593 disabled: stacked.style() != 'expand',
12594 style: 'expand'
12595 },
12597 key: controlLabels.stack_percent || 'Stack %',
12598 metaKey: 'Stack_Percent',
12599 disabled: stacked.style() != 'stack_percent',
12600 style: 'stack_percent'
12602 ];
12604 controlWidth = (controlOptions.length/3) * 260;
12605 controlsData = controlsData.filter(function(d) {
12606 return controlOptions.indexOf(d.metaKey) !== -1;
12607 });
12609 controls
12610 .width( controlWidth )
12611 .color(['#444', '#444', '#444']);
12613 g.select('.nv-controlsWrap')
12614 .datum(controlsData)
12615 .call(controls);
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) {
12635 interactiveLayer
12636 .width(availableWidth)
12637 .height(availableHeight)
12638 .margin({left: margin.left, top: margin.top})
12639 .svgContainer(container)
12640 .xScale(x);
12641 wrap.select(".nv-interactive").call(interactiveLayer);
12644 stacked
12645 .width(availableWidth)
12646 .height(availableHeight);
12648 var stackedWrap = g.select('.nv-stackedWrap')
12649 .datum(data);
12651 stackedWrap.transition().call(stacked);
12653 // Setup Axes
12654 if (showXAxis) {
12655 xAxis.scale(x)
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)
12664 .call(xAxis);
12667 if (showYAxis) {
12668 var ticks;
12669 if (stacked.offset() === 'wiggle') {
12670 ticks = 0;
12672 else {
12673 ticks = nv.utils.calcTicksY(availableHeight/36, data);
12675 yAxis.scale(y)
12676 ._ticks(ticks)
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);
12688 else {
12689 if (oldYTickFormat) {
12690 yAxis.tickFormat(oldYTickFormat);
12691 oldYTickFormat = null;
12695 g.select('.nv-y.nv-axis')
12696 .transition().duration(0)
12697 .call(yAxis);
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;
12708 });
12709 else
12710 data.forEach(function(d,i) {
12711 d.disabled = (i != e.seriesIndex);
12712 });
12714 state.disabled = data.map(function(d) { return !!d.disabled });
12715 dispatch.stateChange(state);
12717 chart.update();
12718 });
12720 legend.dispatch.on('stateChange', function(newState) {
12721 for (var key in newState)
12722 state[key] = newState[key];
12723 dispatch.stateChange(state);
12724 chart.update();
12725 });
12727 controls.dispatch.on('legendClick', function(d,i) {
12728 if (!d.disabled) return;
12730 controlsData = controlsData.map(function(s) {
12731 s.disabled = true;
12732 return s;
12733 });
12734 d.disabled = false;
12736 stacked.style(d.style);
12739 state.style = stacked.style();
12740 dispatch.stateChange(state);
12742 chart.update();
12743 });
12745 interactiveLayer.dispatch.on('elementMousemove', function(e) {
12746 stacked.clearHighlights();
12747 var singlePoint, pointIndex, pointXLocation, allData = [];
12748 data
12749 .filter(function(series, i) {
12750 series.seriesIndex = i;
12751 return !series.disabled;
12752 })
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);
12766 allData.push({
12767 key: series.key,
12768 value: tooltipValue,
12769 color: color(series,series.seriesIndex),
12770 stackedValue: point.display
12771 });
12772 });
12774 allData.reverse();
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;
12790 return;
12792 });
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%");
12808 else {
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)
12819 .data(
12821 value: xValue,
12822 series: allData
12824 )();
12826 interactiveLayer.renderGuideLine(pointXLocation);
12828 });
12830 interactiveLayer.dispatch.on("elementMouseout",function(e) {
12831 stacked.clearHighlights();
12832 });
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];
12840 });
12842 state.disabled = e.disabled;
12845 if (typeof e.style !== 'undefined') {
12846 stacked.style(e.style);
12847 style = e.style;
12850 chart.update();
12851 });
12853 });
12855 renderWatch.renderEnd('stacked Area chart immediate');
12856 return chart;
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);
12867 });
12869 stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
12870 tooltip.hidden(true)
12871 });
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(!!_);
12908 }},
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(_);
12913 }},
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;
12921 }},
12922 duration: {get: function(){return duration;}, set: function(_){
12923 duration = _;
12924 renderWatch.reset(duration);
12925 stacked.duration(duration);
12926 xAxis.duration(duration);
12927 yAxis.duration(duration);
12928 }},
12929 color: {get: function(){return color;}, set: function(_){
12930 color = nv.utils.getColor(_);
12931 legend.color(color);
12932 stacked.color(color);
12933 }},
12934 rightAlignYAxis: {get: function(){return rightAlignYAxis;}, set: function(_){
12935 rightAlignYAxis = _;
12936 yAxis.orient( rightAlignYAxis ? 'right' : 'left');
12937 }},
12938 useInteractiveGuideline: {get: function(){return useInteractiveGuideline;}, set: function(_){
12939 useInteractiveGuideline = !!_;
12940 chart.interactive(!_);
12941 chart.useVoronoi(!_);
12942 stacked.scatter.interactive(!_);
12943 }}
12944 });
12946 nv.utils.inheritOptions(chart, stacked);
12947 nv.utils.initOptions(chart);
12949 return chart;
12950 };
12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
12952 nv.models.sunburst = function() {
12953 "use strict";
12955 //============================================================
12956 // Public Variables with Default Settings
12957 //------------------------------------------------------------
12959 var margin = {top: 0, right: 0, bottom: 0, left: 0}
12960 , width = null
12961 , height = null
12962 , mode = "count"
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
12965 , container = null
12966 , color = nv.utils.defaultColor()
12967 , duration = 500
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()
12975 .sort(null)
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
12987 var rootNode;
12989 //============================================================
12990 // chart function
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;
13002 var path;
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({
13016 data: d,
13017 index: i,
13018 pos: d3.event,
13019 id: id
13020 });
13021 });
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()
13029 .append("path")
13030 .attr("d", arc)
13031 .style("fill", function (d) {
13032 return color((d.children ? d : d.parent).name);
13033 })
13034 .style("stroke", "#FFF")
13035 .on("click", function(d) {
13036 if (prevNode !== node && node !== d) prevNode = node;
13037 node = d;
13038 path.transition()
13039 .duration(duration)
13040 .attrTween("d", arcTweenZoom(d));
13041 })
13042 .each(stash)
13043 .on("dblclick", function(d) {
13044 if (prevNode.parent == d) {
13045 path.transition()
13046 .duration(duration)
13047 .attrTween("d", arcTweenZoom(rootNode));
13049 })
13050 .each(stash)
13051 .on('mouseover', function(d,i){
13052 d3.select(this).classed('hover', true).style('opacity', 0.8);
13053 dispatch.elementMouseover({
13054 data: d,
13055 color: d3.select(this).style("fill")
13056 });
13057 })
13058 .on('mouseout', function(d,i){
13059 d3.select(this).classed('hover', false).style('opacity', 1);
13060 dispatch.elementMouseout({
13061 data: d
13062 });
13063 })
13064 .on('mousemove', function(d,i){
13065 dispatch.elementMousemove({
13066 data: d
13067 });
13068 });
13072 // Setup for switching data: stash the old values for transition.
13073 function stash(d) {
13074 d.x0 = d.x;
13075 d.dx0 = d.dx;
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) {
13083 var b = oi(t);
13084 a.x0 = b.x;
13085 a.dx0 = b.dx;
13086 return arc(b);
13089 if (i == 0) {
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) {
13094 x.domain(xd(t));
13095 return tween(t);
13096 };
13097 } else {
13098 return tween;
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) {
13108 return i
13109 ? function (t) {
13110 return arc(d);
13112 : function (t) {
13113 x.domain(xd(t));
13114 y.domain(yd(t)).range(yr(t));
13115 return arc(d);
13116 };
13117 };
13120 });
13122 renderWatch.renderEnd('sunburst immediate');
13123 return chart;
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;
13147 }},
13148 color: {get: function(){return color;}, set: function(_){
13149 color=nv.utils.getColor(_);
13150 }}
13151 });
13153 nv.utils.initOptions(chart);
13154 return chart;
13155 };
13156 nv.models.sunburstChart = function() {
13157 "use strict";
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}
13167 , width = null
13168 , height = null
13169 , color = nv.utils.defaultColor()
13170 , id = Math.round(Math.random() * 100000)
13171 , defaultState = null
13172 , noData = null
13173 , duration = 250
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) {
13183 return d;
13184 });
13186 //============================================================
13187 // Chart function
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);
13198 var that = this;
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);
13205 else
13206 container.transition().duration(duration).call(chart)
13207 };
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);
13213 return chart;
13214 } else {
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);
13232 });
13234 renderWatch.renderEnd('sunburstChart immediate');
13235 return chart;
13238 //============================================================
13239 // Event Handling/Dispatching (out of chart's scope)
13240 //------------------------------------------------------------
13242 sunburst.dispatch.on('elementMouseover.tooltip', function(evt) {
13243 evt['series'] = {
13244 key: evt.data.name,
13245 value: evt.data.size,
13246 color: evt.color
13247 };
13248 tooltip.data(evt).hidden(false);
13249 });
13251 sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
13252 tooltip.hidden(true);
13253 });
13255 sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
13256 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
13257 });
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(_){
13277 color = _;
13278 sunburst.color(color);
13279 }},
13280 duration: {get: function(){return duration;}, set: function(_){
13281 duration = _;
13282 renderWatch.reset(duration);
13283 sunburst.duration(duration);
13284 }},
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;
13290 }}
13291 });
13292 nv.utils.inheritOptions(chart, sunburst);
13293 nv.utils.initOptions(chart);
13294 return chart;
13295 };
13297 nv.version = "1.8.1";
13298 })();