comparison gtc/nv.d3.js @ 133:41a6e2d99f68

pics3: add blob cache
author paulo
date Thu, 03 Feb 2022 06:39:44 -0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d85e49336d74
1 /* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
2 (function(){
3
4 // set up main nv object
5 var nv = {};
6
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
15
16 nv.dispatch = d3.dispatch('render_start', 'render_end');
17
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 }
29
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 };
39
40 fNOP.prototype = this.prototype;
41 fBound.prototype = new fNOP();
42 return fBound;
43 };
44 }
45
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 });
51
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 }
58
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 };
72
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 };
79
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;
86
87 nv.render.active = true;
88 nv.dispatch.render_start();
89
90 var renderLoop = function() {
91 var chart, graph;
92
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 }
97
98 nv.render.queue.splice(0, i);
99
100 if (nv.render.queue.length) {
101 setTimeout(renderLoop);
102 }
103 else {
104 nv.dispatch.render_end();
105 nv.render.active = false;
106 }
107 };
108
109 setTimeout(renderLoop);
110 };
111
112 nv.render.active = false;
113 nv.render.queue = [];
114
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 })
121
122 or
123
124 nv.addGraph(<generate Function>, <callback Function>)
125
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.
129
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 }
136
137 nv.render.queue.push(obj);
138
139 if (!nv.render.active) {
140 nv.render();
141 }
142 };
143
144 // Node/CommonJS exports
145 if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
146 module.exports = nv;
147 }
148
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 };
164
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.
179
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";
186
187 var tooltip = nv.models.tooltip();
188 tooltip.duration(0).hideDelay(0)._isInteractiveLayer(true).hidden(false);
189
190 //Public settings
191 var width = null;
192 var height = null;
193
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;
203
204 // check if IE by looking for activeX
205 var isMSIE = "ActiveXObject" in window;
206
207
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");
217
218 if (!svgContainer) {
219 return;
220 }
221
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;
238
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 }
251
252 if (d3.event.target.className.baseVal.match("nv-legend")) {
253 mouseOutAnyReason = true;
254 }
255
256 }
257
258 if(subtractMargin) {
259 mouseX -= margin.left;
260 mouseY -= margin.top;
261 }
262
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 ) {
271
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))) {
277
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 }
291
292 var pointXValue = xScale.invert(mouseX);
293 dispatch.elementMousemove({
294 mouseX: mouseX,
295 mouseY: mouseY,
296 pointXValue: pointXValue
297 });
298
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 }
307
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 }
317
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 ;
325
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 }
347
348 layer.dispatch = dispatch;
349 layer.tooltip = tooltip;
350
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 };
357
358 layer.width = function(_) {
359 if (!arguments.length) return width;
360 width = _;
361 return layer;
362 };
363
364 layer.height = function(_) {
365 if (!arguments.length) return height;
366 height = _;
367 return layer;
368 };
369
370 layer.xScale = function(_) {
371 if (!arguments.length) return xScale;
372 xScale = _;
373 return layer;
374 };
375
376 layer.showGuideLine = function(_) {
377 if (!arguments.length) return showGuideLine;
378 showGuideLine = _;
379 return layer;
380 };
381
382 layer.svgContainer = function(_) {
383 if (!arguments.length) return svgContainer;
384 svgContainer = _;
385 return layer;
386 };
387
388 return layer;
389 };
390
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.
393
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.
397
398 Unit tests can be found in: interactiveBisectTest.html
399
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 };
429
430 var bisect = d3.bisector(_cmp).left;
431 var index = d3.max([0, bisect(values,searchVal) - 1]);
432 var currentValue = _xAccessor(values[index]);
433
434 if (typeof currentValue === 'undefined') {
435 currentValue = index;
436 }
437
438 if (currentValue === searchVal) {
439 return index; //found exact match
440 }
441
442 var nextIndex = d3.min([index+1, values.length - 1]);
443 var nextValue = _xAccessor(values[nextIndex]);
444
445 if (typeof nextValue === 'undefined') {
446 nextValue = nextIndex;
447 }
448
449 if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) {
450 return index;
451 } else {
452 return nextIndex
453 }
454 };
455
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.
475
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";
481
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);
486
487 tip(); //just invoke the returned function to render tooltip.
488 */
489 nv.models.tooltip = function() {
490
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 ;
520
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;
525
526 //Generates a unique id when you create a new tooltip() object
527 var id = "nvtooltip-" + Math.floor(Math.random() * 100000);
528
529 //CSS class to specify whether element should not have mouse events.
530 var nvPointerEventsClass = "nv-pointer-events-none";
531
532 //Format function for the tooltip values column
533 var valueFormatter = function(d,i) {
534 return d;
535 };
536
537 //Format function for the tooltip header value.
538 var headerFormatter = function(d) {
539 return d;
540 };
541
542 var keyFormatter = function(d, i) {
543 return d;
544 };
545
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 }
552
553 var table = d3.select(document.createElement("table"));
554 if (headerEnabled) {
555 var theadEnter = table.selectAll("thead")
556 .data([d])
557 .enter().append("thead");
558
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 }
566
567 var tbodyEnter = table.selectAll("tbody")
568 .data([d])
569 .enter().append("tbody");
570
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});
576
577 trowEnter.append("td")
578 .classed("legend-color-guide",true)
579 .append("div")
580 .style("background-color", function(p) { return p.color});
581
582 trowEnter.append("td")
583 .classed("key",true)
584 .html(function(p, i) {return keyFormatter(p.key, i)});
585
586 trowEnter.append("td")
587 .classed("value",true)
588 .html(function(p, i) { return valueFormatter(p.value, i) });
589
590
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 });
601
602 var html = table.node().outerHTML;
603 if (d.footer !== undefined)
604 html += "<div class='footer'>" + d.footer + "</div>";
605 return html;
606
607 };
608
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 };
622
623 var calcTooltipPosition = function(pos) {
624 if (!tooltipElem) return;
625
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;
634
635 windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
636 windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
637
638
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 };
661
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 }
708
709 // adjust tooltip offsets
710 left -= offset.left;
711 top -= offset.top;
712
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);
720
721 var is_hidden = tooltip.style('opacity') < 0.1;
722
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 }
744
745
746
747 });
748 };
749
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];
761
762 position.left = position.left * ratio;
763 position.top = position.top * ratio;
764 }
765 }
766 }
767
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 }
788
789 //Draw the tooltip onto the DOM.
790 function nvtooltip() {
791 if (!enabled) return;
792 if (!dataSeriesExists(data)) return;
793
794 convertViewBoxRatio();
795
796 var left = position.left;
797 var top = (fixedTop !== null) ? fixedTop : position.top;
798
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 }
808
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;
817
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;
833
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 });
843
844 return nvtooltip;
845 }
846
847 nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
848 nvtooltip.options = nv.utils.optionsFunc.bind(nvtooltip);
849
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=_;}},
866
867 // internal use only, set by interactive layer to adjust position.
868 _isInteractiveLayer: {get: function(){return isInteractiveLayer;}, set: function(_){isInteractiveLayer=!!_;}},
869
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 }},
895
896 // read only properties
897 tooltipElem: {get: function(){return tooltipElem;}, set: function(_){}},
898 id: {get: function(){return id;}, set: function(_){}}
899 });
900
901 nv.utils.initOptions(nvtooltip);
902 return nvtooltip;
903 };
904
905 })();
906
907
908 /*
909 Gets the browser window size
910
911 Returns object with height and width properties
912 */
913 nv.utils.windowSize = function() {
914 // Sane defaults
915 var size = {width: 640, height: 480};
916
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 }
923
924 // IE can use depending on mode it is in
925 if (document.compatMode=='CSS1Compat' &&
926 document.documentElement &&
927 document.documentElement.offsetWidth ) {
928
929 size.width = document.documentElement.offsetWidth;
930 size.height = document.documentElement.offsetHeight;
931 return (size);
932 }
933
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 }
940
941 return (size);
942 };
943
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 };
961
962
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();
973
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 };
982
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 };
990
991
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 };
1000
1001
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();
1010
1011 // start at end of default color list and walk back to index 0
1012 var defIndex = defaultColors.length;
1013
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;
1025 }
1026 defIndex = defIndex - 1;
1027 return defaultColors[defIndex];
1028 }
1029 };
1030 };
1031
1032
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) {
1039
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 };
1049
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 });
1055
1056 d3.select(window).on("popstate", function() {
1057 if (d3.event.state) {
1058 load(d3.event.state);
1059 }
1060 });
1061 };
1062
1063
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') {
1072
1073 var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""), 10);
1074 var textLength = svgTextElem.text().length;
1075 return textLength * fontSize * 0.5;
1076 }
1077 return 0;
1078 };
1079
1080
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) {
1090
1091 return 0;
1092 }
1093 return n;
1094 };
1095
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 };
1103
1104
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);
1111 }
1112
1113 var _duration = duration !== undefined ? duration : 250;
1114 var renderStack = [];
1115 var self = this;
1116
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);
1127
1128 if (renderStack.indexOf(model) < 0) {
1129 renderStack.push(model);
1130 }
1131 });
1132 return this;
1133 };
1134
1135 this.reset = function(duration) {
1136 if (duration !== undefined) {
1137 _duration = duration;
1138 }
1139 renderStack = [];
1140 };
1141
1142 this.transition = function(selection, args, duration) {
1143 args = arguments.length > 1 ? [].slice.call(arguments, 1) : [];
1144
1145 if (args.length > 1) {
1146 duration = args.pop();
1147 } else {
1148 duration = _duration !== undefined ? _duration : 250;
1149 }
1150 selection.__rendered = false;
1151
1152 if (renderStack.indexOf(selection) < 0) {
1153 renderStack.push(selection);
1154 }
1155
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;
1168 }
1169
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);
1179 }
1180 });
1181 }
1182 };
1183
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);
1188 }
1189 }
1190
1191 };
1192
1193
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';
1206
1207 if (isObject && !isArray && srcObj) {
1208 nv.utils.deepExtend(dst[key], source[key]);
1209 } else {
1210 dst[key] = source[key];
1211 }
1212 }
1213 });
1214 };
1215
1216
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();
1223 }
1224 var state = {};
1225 var _self = this;
1226 var _setState = function(){};
1227 var _getState = function(){ return {}; };
1228 var init = null;
1229 var changed = null;
1230
1231 this.dispatch = d3.dispatch('change', 'set');
1232
1233 this.dispatch.on('set', function(state){
1234 _setState(state, true);
1235 });
1236
1237 this.getter = function(fn){
1238 _getState = fn;
1239 return this;
1240 };
1241
1242 this.setter = function(fn, callback) {
1243 if (!callback) {
1244 callback = function(){};
1245 }
1246 _setState = function(state, update){
1247 fn(state);
1248 if (update) {
1249 callback();
1250 }
1251 };
1252 return this;
1253 };
1254
1255 this.init = function(state){
1256 init = init || {};
1257 nv.utils.deepExtend(init, state);
1258 };
1259
1260 var _set = function(){
1261 var settings = _getState();
1262
1263 if (JSON.stringify(settings) === JSON.stringify(state)) {
1264 return false;
1265 }
1266
1267 for (var key in settings) {
1268 if (state[key] === undefined) {
1269 state[key] = {};
1270 }
1271 state[key] = settings[key];
1272 changed = true;
1273 }
1274 return true;
1275 };
1276
1277 this.update = function(){
1278 if (init) {
1279 _setState(init, false);
1280 init = null;
1281 }
1282 if (_set.call(this)) {
1283 this.dispatch.change(state);
1284 }
1285 };
1286
1287 };
1288
1289
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 });
1297
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);
1306 }
1307 }).bind(this));
1308 }
1309 return this;
1310 };
1311
1312
1313 /*
1314 numTicks: requested number of ticks
1315 data: the chart data
1316
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;
1327 }
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 };
1339
1340
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 };
1348
1349
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');
1354
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] = _;
1375 }
1376 return chart;
1377 }
1378 }
1379 };
1380
1381
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]);
1392 }
1393 };
1394
1395
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 };
1407
1408
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 };
1417
1418
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();
1425
1426
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);
1440 }
1441 }
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 };
1454
1455
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 };
1476
1477
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 };
1484
1485
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 };
1492
1493
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 };
1500
1501
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 };
1508
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 };
1515
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;
1528
1529 //Remove any previously created chart components
1530 container.selectAll('g').remove();
1531
1532 var noDataText = container.selectAll('.nv-noData').data(data);
1533
1534 noDataText.enter().append('text')
1535 .attr('class', 'nvd3 nv-noData')
1536 .attr('dy', '-.7em')
1537 .style('text-anchor', 'middle');
1538
1539 noDataText
1540 .attr('x', x)
1541 .attr('y', y)
1542 .text(function(t){ return t; });
1543 };
1544
1545 nv.models.axis = function() {
1546 "use strict";
1547
1548 //============================================================
1549 // Public Variables with Default Settings
1550 //------------------------------------------------------------
1551
1552 var axis = d3.svg.axis();
1553 var scale = d3.scale.linear();
1554
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')
1568 ;
1569 axis
1570 .scale(scale)
1571 .orient('bottom')
1572 .tickFormat(function(d) { return d })
1573 ;
1574
1575 //============================================================
1576 // Private Variables
1577 //------------------------------------------------------------
1578
1579 var scale0;
1580 var renderWatch = nv.utils.renderWatch(dispatch, duration);
1581
1582 function chart(selection) {
1583 renderWatch.reset();
1584 selection.each(function(data) {
1585 var container = d3.select(this);
1586 nv.utils.initSVG(container);
1587
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');
1593
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);
1598
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);
1601
1602 scale0 = scale0 || axis.scale();
1603
1604 var fmt = axis.tickFormat();
1605 if (fmt == null) {
1606 fmt = scale0.tickFormat();
1607 }
1608
1609 var axisLabel = g.selectAll('text.nv-axislabel')
1610 .data([axisLabelText || null]);
1611 axisLabel.exit().remove();
1612
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]);
1625 }
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 });
1653 }
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');
1677 }
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]);
1685 }
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 });
1716 }
1717 if (staggerLabels)
1718 xTicks
1719 .attr('transform', function(d,i) {
1720 return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')'
1721 });
1722
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);
1758 }
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);
1802 }
1803 break;
1804 }
1805 axisLabel.text(function(d) { return d });
1806
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);
1815
1816 d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1817 }
1818 });
1819
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 });
1825 }
1826 }
1827
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);
1842 }
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!!
1851 }
1852 });
1853 }
1854
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);
1866
1867 //store old scales for use in transitions on update
1868 scale0 = scale.copy();
1869
1870 });
1871
1872 renderWatch.renderEnd('axis immediate');
1873 return chart;
1874 }
1875
1876 //============================================================
1877 // Expose Public Variables
1878 //------------------------------------------------------------
1879
1880 // expose chart's sub-components
1881 chart.axis = axis;
1882 chart.dispatch = dispatch;
1883
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=_;}},
1896
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 });
1915
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']);
1919
1920 return chart;
1921 };
1922 nv.models.boxPlot = function() {
1923 "use strict";
1924
1925 //============================================================
1926 // Public Variables with Default Settings
1927 //------------------------------------------------------------
1928
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
1946 ;
1947
1948 //============================================================
1949 // Private Variables
1950 //------------------------------------------------------------
1951
1952 var x0, y0;
1953 var renderWatch = nv.utils.renderWatch(dispatch, duration);
1954
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;
1960
1961 container = d3.select(this);
1962 nv.utils.initSVG(container);
1963
1964 // Setup Scales
1965 x .domain(xDomain || data.map(function(d,i) { return getX(d,i); }))
1966 .rangeBands(xRange || [0, availableWidth], .1);
1967
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)
1972
1973 // lower values
1974 var yMin = d3.min(data.map(function(d) {
1975 var min_arr = [];
1976
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); }
1980
1981 return d3.min(min_arr);
1982 }));
1983
1984 // upper values
1985 var yMax = d3.max(data.map(function(d) {
1986 var max_arr = [];
1987
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); }
1991
1992 return d3.max(max_arr);
1993 }));
1994
1995 yData = [ yMin, yMax ] ;
1996 }
1997
1998 y.domain(yDomain || yData);
1999 y.range(yRange || [availableHeight, 0]);
2000
2001 //store old scales if they exist
2002 x0 = x0 || x;
2003 y0 = y0 || y.copy().range([y(0),y(0)]);
2004
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 + ')');
2009
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();
2025
2026 // ----- add the SVG elements for each boxPlot -----
2027
2028 // conditionally append whisker lines
2029 boxEnter.each(function(d,i) {
2030 var box = d3.select(this);
2031
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);
2037
2038 box.append('line')
2039 .style('stroke', (d.color) ? d.color : color(d,i))
2040 .attr('class', 'nv-boxplot-tick nv-boxplot-' + key);
2041 }
2042 });
2043 });
2044
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 });
2070
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();
2078
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; };
2082
2083 // update whisker lines and ticks
2084 ['low', 'high'].forEach(function(key) {
2085 var endpoint = (key === 'low') ? 'Q1' : 'Q3';
2086
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]); });
2093
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 });
2101
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 });
2122
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 });
2160
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 )
2167
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) });
2171
2172 // median line
2173 boxEnter.append('line').attr('class', 'nv-boxplot-median');
2174
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); });
2181
2182 //store old scales for use in transitions on update
2183 x0 = x.copy();
2184 y0 = y.copy();
2185 });
2186
2187 renderWatch.renderEnd('nv-boxplot immediate');
2188 return chart;
2189 }
2190
2191 //============================================================
2192 // Expose Public Variables
2193 //------------------------------------------------------------
2194
2195 chart.dispatch = dispatch;
2196 chart.options = nv.utils.optionsFunc.bind(chart);
2197
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=_;}},
2213
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 });
2229
2230 nv.utils.initOptions(chart);
2231
2232 return chart;
2233 };
2234 nv.models.boxPlotChart = function() {
2235 "use strict";
2236
2237 //============================================================
2238 // Public Variables with Default Settings
2239 //------------------------------------------------------------
2240
2241 var boxplot = nv.models.boxPlot()
2242 , xAxis = nv.models.axis()
2243 , yAxis = nv.models.axis()
2244 ;
2245
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
2260 ;
2261
2262 xAxis
2263 .orient('bottom')
2264 .showMaxMin(false)
2265 .tickFormat(function(d) { return d })
2266 ;
2267 yAxis
2268 .orient((rightAlignYAxis) ? 'right' : 'left')
2269 .tickFormat(d3.format(',.1f'))
2270 ;
2271
2272 tooltip.duration(0);
2273
2274 //============================================================
2275 // Private Variables
2276 //------------------------------------------------------------
2277
2278 var renderWatch = nv.utils.renderWatch(dispatch, duration);
2279
2280 function chart(selection) {
2281 renderWatch.reset();
2282 renderWatch.models(boxplot);
2283 if (showXAxis) renderWatch.models(xAxis);
2284 if (showYAxis) renderWatch.models(yAxis);
2285
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;
2294
2295 chart.update = function() {
2296 dispatch.beforeUpdate();
2297 container.transition().duration(duration).call(chart);
2298 };
2299 chart.container = this;
2300
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]);
2305
2306 noDataText.enter().append('text')
2307 .attr('class', 'nvd3 nv-noData')
2308 .attr('dy', '-.7em')
2309 .style('text-anchor', 'middle');
2310
2311 noDataText
2312 .attr('x', margin.left + availableWidth / 2)
2313 .attr('y', margin.top + availableHeight / 2)
2314 .text(function(d) { return d });
2315
2316 return chart;
2317 } else {
2318 container.selectAll('.nv-noData').remove();
2319 }
2320
2321 // Setup Scales
2322 x = boxplot.xScale();
2323 y = boxplot.yScale().clamp(true);
2324
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');
2330
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');
2335
2336 gEnter.append('g').attr('class', 'nv-barsWrap');
2337
2338 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2339
2340 if (rightAlignYAxis) {
2341 g.select(".nv-y.nv-axis")
2342 .attr("transform", "translate(" + availableWidth + ",0)");
2343 }
2344
2345 // Main Chart Component(s)
2346 boxplot
2347 .width(availableWidth)
2348 .height(availableHeight);
2349
2350 var barsWrap = g.select('.nv-barsWrap')
2351 .datum(data.filter(function(d) { return !d.disabled }))
2352
2353 barsWrap.transition().call(boxplot);
2354
2355
2356 defsEnter.append('clipPath')
2357 .attr('id', 'nv-x-label-clip-' + boxplot.id())
2358 .append('rect');
2359
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 ));
2364
2365 // Setup Axes
2366 if (showXAxis) {
2367 xAxis
2368 .scale(x)
2369 .ticks( nv.utils.calcTicksX(availableWidth/100, data) )
2370 .tickSize(-availableHeight, 0);
2371
2372 g.select('.nv-x.nv-axis').attr('transform', 'translate(0,' + y.range()[0] + ')');
2373 g.select('.nv-x.nv-axis').call(xAxis);
2374
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') + ')' })
2380 }
2381 }
2382
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);
2388
2389 g.select('.nv-y.nv-axis').call(yAxis);
2390 }
2391
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))
2398 ;
2399
2400 //============================================================
2401 // Event Handling/Dispatching (in chart's scope)
2402 //------------------------------------------------------------
2403 });
2404
2405 renderWatch.renderEnd('nv-boxplot chart immediate');
2406 return chart;
2407 }
2408
2409 //============================================================
2410 // Event Handling/Dispatching (out of chart's scope)
2411 //------------------------------------------------------------
2412
2413 boxplot.dispatch.on('elementMouseover.tooltip', function(evt) {
2414 tooltip.data(evt).hidden(false);
2415 });
2416
2417 boxplot.dispatch.on('elementMouseout.tooltip', function(evt) {
2418 tooltip.data(evt).hidden(true);
2419 });
2420
2421 boxplot.dispatch.on('elementMousemove.tooltip', function(evt) {
2422 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2423 });
2424
2425 //============================================================
2426 // Expose Public Variables
2427 //------------------------------------------------------------
2428
2429 chart.dispatch = dispatch;
2430 chart.boxplot = boxplot;
2431 chart.xAxis = xAxis;
2432 chart.yAxis = yAxis;
2433 chart.tooltip = tooltip;
2434
2435 chart.options = nv.utils.optionsFunc.bind(chart);
2436
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=_;}},
2447
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 });
2471
2472 nv.utils.inheritOptions(chart, boxplot);
2473 nv.utils.initOptions(chart);
2474
2475 return chart;
2476 }
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/
2480
2481 nv.models.bullet = function() {
2482 "use strict";
2483
2484 //============================================================
2485 // Public Variables with Default Settings
2486 //------------------------------------------------------------
2487
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')
2504 ;
2505
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;
2510
2511 container = d3.select(this);
2512 nv.utils.initSVG(container);
2513
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();
2520
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]);
2526
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());
2531
2532 // Stash the new scale.
2533 this.__chart__ = x1;
2534
2535 var rangeMin = d3.min(rangez), //rangez[2]
2536 rangeMax = d3.max(rangez), //rangez[0]
2537 rangeAvg = rangez[1];
2538
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');
2544
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');
2549
2550 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2551
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) };
2556
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)
2562
2563 g.select('rect.nv-rangeAvg')
2564 .attr('height', availableHeight)
2565 .attr('width', w1(rangeAvg))
2566 .attr('x', xp1(rangeAvg))
2567 .datum(rangeAvg)
2568
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)
2576
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 });
2606
2607 var h3 = availableHeight / 6;
2608
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 })
2627
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 });
2643
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 });
2669
2670 return chart;
2671 }
2672
2673 //============================================================
2674 // Expose Public Variables
2675 //------------------------------------------------------------
2676
2677 chart.dispatch = dispatch;
2678 chart.options = nv.utils.optionsFunc.bind(chart);
2679
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=_;}},
2689
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 });
2705
2706 nv.utils.initOptions(chart);
2707 return chart;
2708 };
2709
2710
2711
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";
2717
2718 //============================================================
2719 // Public Variables with Default Settings
2720 //------------------------------------------------------------
2721
2722 var bullet = nv.models.bullet();
2723 var tooltip = nv.models.tooltip();
2724
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')
2737 ;
2738
2739 tooltip.duration(0).headerEnabled(false);
2740
2741 function chart(selection) {
2742 selection.each(function(d, i) {
2743 var container = d3.select(this);
2744 nv.utils.initSVG(container);
2745
2746 var availableWidth = nv.utils.availableWidth(width, container, margin),
2747 availableHeight = height - margin.top - margin.bottom,
2748 that = this;
2749
2750 chart.update = function() { chart(selection) };
2751 chart.container = this;
2752
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();
2759 }
2760
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);
2764
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');
2770
2771 gEnter.append('g').attr('class', 'nv-bulletWrap');
2772 gEnter.append('g').attr('class', 'nv-titles');
2773
2774 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2775
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]);
2780
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());
2785
2786 // Stash the new scale.
2787 this.__chart__ = x1;
2788
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)) };
2791
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; });
2798
2799 title.append('text')
2800 .attr('class', 'nv-subtitle')
2801 .attr('dy', '1em')
2802 .text(function(d) { return d.subtitle; });
2803
2804 bullet
2805 .width(availableWidth)
2806 .height(availableHeight)
2807
2808 var bulletWrap = g.select('.nv-bulletWrap');
2809 d3.transition(bulletWrap).call(bullet);
2810
2811 // Compute the tick format.
2812 var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2813
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 });
2819
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);
2825
2826 tickEnter.append('line')
2827 .attr('y1', availableHeight)
2828 .attr('y2', availableHeight * 7 / 6);
2829
2830 tickEnter.append('text')
2831 .attr('text-anchor', 'middle')
2832 .attr('dy', '1em')
2833 .attr('y', availableHeight * 7 / 6)
2834 .text(format);
2835
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);
2840
2841 tickUpdate.select('line')
2842 .attr('y1', availableHeight)
2843 .attr('y2', availableHeight * 7 / 6);
2844
2845 tickUpdate.select('text')
2846 .attr('y', availableHeight * 7 / 6);
2847
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 });
2854
2855 d3.timer.flush();
2856 return chart;
2857 }
2858
2859 //============================================================
2860 // Event Handling/Dispatching (out of chart's scope)
2861 //------------------------------------------------------------
2862
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 });
2871
2872 bullet.dispatch.on('elementMouseout.tooltip', function(evt) {
2873 tooltip.hidden(true);
2874 });
2875
2876 bullet.dispatch.on('elementMousemove.tooltip', function(evt) {
2877 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
2878 });
2879
2880 //============================================================
2881 // Expose Public Variables
2882 //------------------------------------------------------------
2883
2884 chart.bullet = bullet;
2885 chart.dispatch = dispatch;
2886 chart.tooltip = tooltip;
2887
2888 chart.options = nv.utils.optionsFunc.bind(chart);
2889
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=_;}},
2900
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 }},
2912
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 });
2925
2926 nv.utils.inheritOptions(chart, bullet);
2927 nv.utils.initOptions(chart);
2928
2929 return chart;
2930 };
2931
2932
2933
2934 nv.models.candlestickBar = function() {
2935 "use strict";
2936
2937 //============================================================
2938 // Public Variables with Default Settings
2939 //------------------------------------------------------------
2940
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')
2965 ;
2966
2967 //============================================================
2968 // Private Variables
2969 //------------------------------------------------------------
2970
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);
2976
2977 nv.utils.initSVG(container);
2978
2979 // Width of the candlestick bars.
2980 var barWidth = (availableWidth / data[0].values.length) * .45;
2981
2982 // Setup Scales
2983 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
2984
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]);
2989
2990 y.domain(yDomain || [
2991 d3.min(data[0].values.map(getLow).concat(forceY)),
2992 d3.max(data[0].values.map(getHigh).concat(forceY))
2993 ]
2994 ).range(yRange || [availableHeight, 0]);
2995
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]);
3001
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]);
3006
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');
3013
3014 gEnter.append('g').attr('class', 'nv-ticks');
3015
3016 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3017
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 });
3027
3028 defsEnter.append('clipPath')
3029 .attr('id', 'nv-chart-clip-path-' + id)
3030 .append('rect');
3031
3032 wrap.select('#nv-chart-clip-path-' + id + ' rect')
3033 .attr('width', availableWidth)
3034 .attr('height', availableHeight);
3035
3036 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3037
3038 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
3039 .data(function(d) { return d });
3040 ticks.exit().remove();
3041
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});
3045
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)); });
3053
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 });
3069
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)); });
3076
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 });
3092
3093 return chart;
3094 }
3095
3096
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)
3102 ;
3103 };
3104
3105 chart.clearHighlights = function() {
3106 container.select(".nv-candlestickBar .nv-tick.hover")
3107 .classed("hover", false)
3108 ;
3109 };
3110
3111 //============================================================
3112 // Expose Public Variables
3113 //------------------------------------------------------------
3114
3115 chart.dispatch = dispatch;
3116 chart.options = nv.utils.optionsFunc.bind(chart);
3117
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=_;}},
3134
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=_;}},
3141
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 });
3153
3154 nv.utils.initOptions(chart);
3155 return chart;
3156 };
3157
3158 nv.models.cumulativeLineChart = function() {
3159 "use strict";
3160
3161 //============================================================
3162 // Public Variables with Default Settings
3163 //------------------------------------------------------------
3164
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()
3172 ;
3173
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.
3196 ;
3197
3198 state.index = 0;
3199 state.rescaleY = rescaleY;
3200
3201 xAxis.orient('bottom').tickPadding(7);
3202 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
3203
3204 tooltip.valueFormatter(function(d, i) {
3205 return yAxis.tickFormat()(d, i);
3206 }).headerFormatter(function(d, i) {
3207 return xAxis.tickFormat()(d, i);
3208 });
3209
3210 controls.updateState(false);
3211
3212 //============================================================
3213 // Private Variables
3214 //------------------------------------------------------------
3215
3216 var dx = d3.scale.linear()
3217 , index = {i: 0, x: 0}
3218 , renderWatch = nv.utils.renderWatch(dispatch, duration)
3219 ;
3220
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 };
3228 }
3229 };
3230
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 });
3241 }
3242 };
3243
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;
3254
3255 var availableWidth = nv.utils.availableWidth(width, container, margin),
3256 availableHeight = nv.utils.availableHeight(height, container, margin);
3257
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;
3265
3266 state
3267 .setter(stateSetter(data), chart.update)
3268 .getter(stateGetter(data))
3269 .update();
3270
3271 // DEPRECATED set state.disableddisabled
3272 state.disabled = data.map(function(d) { return !!d.disabled });
3273
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];
3282 }
3283 }
3284
3285 var indexDrag = d3.behavior.drag()
3286 .on('dragstart', dragStart)
3287 .on('drag', dragMove)
3288 .on('dragend', dragEnd);
3289
3290
3291 function dragStart(d,i) {
3292 d3.select(chart.container)
3293 .style('cursor', 'ew-resize');
3294 }
3295
3296 function dragMove(d,i) {
3297 index.x = d3.event.x;
3298 index.i = Math.round(dx.invert(index.x));
3299 updateZero();
3300 }
3301
3302 function dragEnd(d,i) {
3303 d3.select(chart.container)
3304 .style('cursor', 'auto');
3305
3306 // update state and send stateChange with new index
3307 state.index = index.i;
3308 dispatch.stateChange(state);
3309 }
3310
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();
3317 }
3318
3319 // Setup Scales
3320 x = lines.xScale();
3321 y = lines.yScale();
3322
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());
3328
3329 //account for series being disabled when losing 95% or more
3330 if (initialDomain[0] < -.95) initialDomain[0] = -.95;
3331
3332 return [
3333 (initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
3334 (initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
3335 ];
3336 });
3337
3338 var completeDomain = [
3339 d3.min(seriesDomains, function(d) { return d[0] }),
3340 d3.max(seriesDomains, function(d) { return d[1] })
3341 ];
3342
3343 lines.yDomain(completeDomain);
3344 } else {
3345 lines.yDomain(null);
3346 }
3347
3348 dx.domain([0, data[0].values.length - 1]) //Assumes all series have same length
3349 .range([0, availableWidth])
3350 .clamp(true);
3351
3352 var data = indexify(index.i, data);
3353
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');
3359
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');
3368
3369 // Legend
3370 if (showLegend) {
3371 legend.width(availableWidth);
3372
3373 g.select('.nv-legendWrap')
3374 .datum(data)
3375 .call(legend);
3376
3377 if ( margin.top != legend.height()) {
3378 margin.top = legend.height();
3379 availableHeight = nv.utils.availableHeight(height, container, margin);
3380 }
3381
3382 g.select('.nv-legendWrap')
3383 .attr('transform', 'translate(0,' + (-margin.top) +')')
3384 }
3385
3386 // Controls
3387 if (showControls) {
3388 var controlsData = [
3389 { key: 'Re-scale y-axis', disabled: !rescaleY }
3390 ];
3391
3392 controls
3393 .width(140)
3394 .color(['#444', '#444', '#444'])
3395 .rightAlign(false)
3396 .margin({top: 5, right: 0, bottom: 5, left: 20})
3397 ;
3398
3399 g.select('.nv-controlsWrap')
3400 .datum(controlsData)
3401 .attr('transform', 'translate(0,' + (-margin.top) +')')
3402 .call(controls);
3403 }
3404
3405 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3406
3407 if (rightAlignYAxis) {
3408 g.select(".nv-y.nv-axis")
3409 .attr("transform", "translate(" + availableWidth + ",0)");
3410 }
3411
3412 // Show error if series goes below 100%
3413 var tempDisabled = data.filter(function(d) { return d.tempDisabled });
3414
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.');
3422 }
3423
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);
3433 }
3434
3435 gEnter.select('.nv-background')
3436 .append('rect');
3437
3438 g.select('.nv-background rect')
3439 .attr('width', availableWidth)
3440 .attr('height', availableHeight);
3441
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; }));
3450
3451 var linesWrap = g.select('.nv-linesWrap')
3452 .datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
3453
3454 linesWrap.call(lines);
3455
3456 //Store a series index number in the data array.
3457 data.forEach(function(d,i) {
3458 d.seriesIndex = i;
3459 });
3460
3461 var avgLineData = data.filter(function(d) {
3462 return !d.disabled && !!average(d);
3463 });
3464
3465 var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
3466 .data(avgLineData, function(d) { return d.key; });
3467
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 };
3475
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);
3487
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);
3499
3500 avgLines.exit().remove();
3501
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);
3512
3513 indexLine
3514 .attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
3515 .attr('height', availableHeight);
3516
3517 // Setup Axes
3518 if (showXAxis) {
3519 xAxis
3520 .scale(x)
3521 ._ticks( nv.utils.calcTicksX(availableWidth/70, data) )
3522 .tickSize(-availableHeight, 0);
3523
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);
3528 }
3529
3530 if (showYAxis) {
3531 yAxis
3532 .scale(y)
3533 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
3534 .tickSize( -availableWidth, 0);
3535
3536 g.select('.nv-y.nv-axis')
3537 .call(yAxis);
3538 }
3539
3540 //============================================================
3541 // Event Handling/Dispatching (in chart's scope)
3542 //------------------------------------------------------------
3543
3544 function updateZero() {
3545 indexLine
3546 .data([index]);
3547
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);
3554 }
3555
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));
3560
3561 // update state and send stateChange with new index
3562 state.index = index.i;
3563 dispatch.stateChange(state);
3564
3565 updateZero();
3566 });
3567
3568 lines.dispatch.on('elementClick', function(e) {
3569 index.i = e.pointIndex;
3570 index.x = dx(index.i);
3571
3572 // update state and send stateChange with new index
3573 state.index = index.i;
3574 dispatch.stateChange(state);
3575
3576 updateZero();
3577 });
3578
3579 controls.dispatch.on('legendClick', function(d,i) {
3580 d.disabled = !d.disabled;
3581 rescaleY = !d.disabled;
3582
3583 state.rescaleY = rescaleY;
3584 dispatch.stateChange(state);
3585 chart.update();
3586 });
3587
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 });
3594
3595 interactiveLayer.dispatch.on('elementMousemove', function(e) {
3596 lines.clearHighlights();
3597 var singlePoint, pointIndex, pointXLocation, allData = [];
3598
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 });
3617
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;
3626 }
3627
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(
3636 {
3637 value: xValue,
3638 series: allData
3639 }
3640 )();
3641
3642 interactiveLayer.renderGuideLine(pointXLocation);
3643 });
3644
3645 interactiveLayer.dispatch.on("elementMouseout",function(e) {
3646 lines.clearHighlights();
3647 });
3648
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 });
3655
3656 state.disabled = e.disabled;
3657 }
3658
3659 if (typeof e.index !== 'undefined') {
3660 index.i = e.index;
3661 index.x = dx(index.i);
3662
3663 state.index = e.index;
3664
3665 indexLine
3666 .data([index]);
3667 }
3668
3669 if (typeof e.rescaleY !== 'undefined') {
3670 rescaleY = e.rescaleY;
3671 }
3672
3673 chart.update();
3674 });
3675
3676 });
3677
3678 renderWatch.renderEnd('cumulativeLineChart immediate');
3679
3680 return chart;
3681 }
3682
3683 //============================================================
3684 // Event Handling/Dispatching (out of chart's scope)
3685 //------------------------------------------------------------
3686
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 });
3696
3697 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
3698 tooltip.hidden(true)
3699 });
3700
3701 //============================================================
3702 // Functions
3703 //------------------------------------------------------------
3704
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;
3712 }
3713 var indexValue = line.values[idx];
3714 if (indexValue == null) {
3715 return line;
3716 }
3717 var v = indexifyYGetter(indexValue, idx);
3718
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)
3722
3723 line.tempDisabled = true;
3724 return line;
3725 }
3726
3727 line.tempDisabled = false;
3728
3729 line.values = line.values.map(function(point, pointIndex) {
3730 point.display = {'y': (indexifyYGetter(point, pointIndex) - v) / (1 + v) };
3731 return point;
3732 });
3733
3734 return line;
3735 })
3736 }
3737
3738 //============================================================
3739 // Expose Public Variables
3740 //------------------------------------------------------------
3741
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;
3752
3753 chart.options = nv.utils.optionsFunc.bind(chart);
3754
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=_;}},
3768
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 }},
3780
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);
3797 }
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 });
3811
3812 nv.utils.inheritOptions(chart, lines);
3813 nv.utils.initOptions(chart);
3814
3815 return chart;
3816 };
3817 //TODO: consider deprecating by adding necessary features to multiBar model
3818 nv.models.discreteBar = function() {
3819 "use strict";
3820
3821 //============================================================
3822 // Public Variables with Default Settings
3823 //------------------------------------------------------------
3824
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
3845 ;
3846
3847 //============================================================
3848 // Private Variables
3849 //------------------------------------------------------------
3850
3851 var x0, y0;
3852 var renderWatch = nv.utils.renderWatch(dispatch, duration);
3853
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;
3859
3860 container = d3.select(this);
3861 nv.utils.initSVG(container);
3862
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 });
3869
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 });
3878
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)));
3882
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]);
3886
3887 //store old scales if they exist
3888 x0 = x0 || x;
3889 y0 = y0 || y.copy().range([y(0),y(0)]);
3890
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');
3896
3897 gEnter.append('g').attr('class', 'nv-groups');
3898 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3899
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);
3918
3919 var bars = groups.selectAll('g.nv-bar')
3920 .data(function(d) { return d.values });
3921 bars.exit().remove();
3922
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 });
3966
3967 barsEnter.append('rect')
3968 .attr('height', 0)
3969 .attr('width', x.rangeBand() * .9 / data.length )
3970
3971 if (showValues) {
3972 barsEnter.append('text')
3973 .attr('text-anchor', 'middle')
3974 ;
3975
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 })
3981
3982 ;
3983 } else {
3984 bars.selectAll('text').remove();
3985 }
3986
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));
4004
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 });
4011
4012
4013 //store old scales for use in transitions on update
4014 x0 = x.copy();
4015 y0 = y.copy();
4016
4017 });
4018
4019 renderWatch.renderEnd('discreteBar immediate');
4020 return chart;
4021 }
4022
4023 //============================================================
4024 // Expose Public Variables
4025 //------------------------------------------------------------
4026
4027 chart.dispatch = dispatch;
4028 chart.options = nv.utils.optionsFunc.bind(chart);
4029
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=_;}},
4047
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 });
4063
4064 nv.utils.initOptions(chart);
4065
4066 return chart;
4067 };
4068
4069 nv.models.discreteBarChart = function() {
4070 "use strict";
4071
4072 //============================================================
4073 // Public Variables with Default Settings
4074 //------------------------------------------------------------
4075
4076 var discretebar = nv.models.discreteBar()
4077 , xAxis = nv.models.axis()
4078 , yAxis = nv.models.axis()
4079 , tooltip = nv.models.tooltip()
4080 ;
4081
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
4095 ;
4096
4097 xAxis
4098 .orient('bottom')
4099 .showMaxMin(false)
4100 .tickFormat(function(d) { return d })
4101 ;
4102 yAxis
4103 .orient((rightAlignYAxis) ? 'right' : 'left')
4104 .tickFormat(d3.format(',.1f'))
4105 ;
4106
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 });
4116
4117 //============================================================
4118 // Private Variables
4119 //------------------------------------------------------------
4120
4121 var renderWatch = nv.utils.renderWatch(dispatch, duration);
4122
4123 function chart(selection) {
4124 renderWatch.reset();
4125 renderWatch.models(discretebar);
4126 if (showXAxis) renderWatch.models(xAxis);
4127 if (showYAxis) renderWatch.models(yAxis);
4128
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);
4135
4136 chart.update = function() {
4137 dispatch.beforeUpdate();
4138 container.transition().duration(duration).call(chart);
4139 };
4140 chart.container = this;
4141
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();
4148 }
4149
4150 // Setup Scales
4151 x = discretebar.xScale();
4152 y = discretebar.yScale().clamp(true);
4153
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');
4159
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');
4164
4165 gEnter.append('g').attr('class', 'nv-barsWrap');
4166
4167 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4168
4169 if (rightAlignYAxis) {
4170 g.select(".nv-y.nv-axis")
4171 .attr("transform", "translate(" + availableWidth + ",0)");
4172 }
4173
4174 // Main Chart Component(s)
4175 discretebar
4176 .width(availableWidth)
4177 .height(availableHeight);
4178
4179 var barsWrap = g.select('.nv-barsWrap')
4180 .datum(data.filter(function(d) { return !d.disabled }));
4181
4182 barsWrap.transition().call(discretebar);
4183
4184
4185 defsEnter.append('clipPath')
4186 .attr('id', 'nv-x-label-clip-' + discretebar.id())
4187 .append('rect');
4188
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 ));
4193
4194 // Setup Axes
4195 if (showXAxis) {
4196 xAxis
4197 .scale(x)
4198 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
4199 .tickSize(-availableHeight, 0);
4200
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);
4204
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') + ')' })
4210 }
4211 }
4212
4213 if (showYAxis) {
4214 yAxis
4215 .scale(y)
4216 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
4217 .tickSize( -availableWidth, 0);
4218
4219 g.select('.nv-y.nv-axis').call(yAxis);
4220 }
4221
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))
4228 ;
4229 });
4230
4231 renderWatch.renderEnd('discreteBar chart immediate');
4232 return chart;
4233 }
4234
4235 //============================================================
4236 // Event Handling/Dispatching (out of chart's scope)
4237 //------------------------------------------------------------
4238
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 });
4247
4248 discretebar.dispatch.on('elementMouseout.tooltip', function(evt) {
4249 tooltip.hidden(true);
4250 });
4251
4252 discretebar.dispatch.on('elementMousemove.tooltip', function(evt) {
4253 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
4254 });
4255
4256 //============================================================
4257 // Expose Public Variables
4258 //------------------------------------------------------------
4259
4260 chart.dispatch = dispatch;
4261 chart.discretebar = discretebar;
4262 chart.xAxis = xAxis;
4263 chart.yAxis = yAxis;
4264 chart.tooltip = tooltip;
4265
4266 chart.options = nv.utils.optionsFunc.bind(chart);
4267
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=_;}},
4276
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 }},
4288
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 });
4312
4313 nv.utils.inheritOptions(chart, discretebar);
4314 nv.utils.initOptions(chart);
4315
4316 return chart;
4317 }
4318
4319 nv.models.distribution = function() {
4320 "use strict";
4321 //============================================================
4322 // Public Variables with Default Settings
4323 //------------------------------------------------------------
4324
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')
4335 ;
4336
4337 //============================================================
4338
4339
4340 //============================================================
4341 // Private Variables
4342 //------------------------------------------------------------
4343
4344 var scale0;
4345 var renderWatch = nv.utils.renderWatch(dispatch, duration);
4346
4347 //============================================================
4348
4349
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);
4357
4358 //------------------------------------------------------------
4359 // Setup Scales
4360
4361 scale0 = scale0 || scale;
4362
4363 //------------------------------------------------------------
4364
4365
4366 //------------------------------------------------------------
4367 // Setup containers and skeleton of chart
4368
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');
4373
4374 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
4375
4376 //------------------------------------------------------------
4377
4378
4379 var distWrap = g.selectAll('g.nv-dist')
4380 .data(function(d) { return d }, function(d) { return d.key });
4381
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) });
4386
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)) })
4406
4407
4408 scale0 = scale.copy();
4409
4410 });
4411 renderWatch.renderEnd('distribution immediate');
4412 return chart;
4413 }
4414
4415
4416 //============================================================
4417 // Expose Public Variables
4418 //------------------------------------------------------------
4419 chart.options = nv.utils.optionsFunc.bind(chart);
4420 chart.dispatch = dispatch;
4421
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 };
4430
4431 chart.width = function(_) {
4432 if (!arguments.length) return width;
4433 width = _;
4434 return chart;
4435 };
4436
4437 chart.axis = function(_) {
4438 if (!arguments.length) return axis;
4439 axis = _;
4440 return chart;
4441 };
4442
4443 chart.size = function(_) {
4444 if (!arguments.length) return size;
4445 size = _;
4446 return chart;
4447 };
4448
4449 chart.getData = function(_) {
4450 if (!arguments.length) return getData;
4451 getData = d3.functor(_);
4452 return chart;
4453 };
4454
4455 chart.scale = function(_) {
4456 if (!arguments.length) return scale;
4457 scale = _;
4458 return chart;
4459 };
4460
4461 chart.color = function(_) {
4462 if (!arguments.length) return color;
4463 color = nv.utils.getColor(_);
4464 return chart;
4465 };
4466
4467 chart.duration = function(_) {
4468 if (!arguments.length) return duration;
4469 duration = _;
4470 renderWatch.reset(duration);
4471 return chart;
4472 };
4473 //============================================================
4474
4475
4476 return chart;
4477 }
4478 nv.models.furiousLegend = function() {
4479 "use strict";
4480
4481 //============================================================
4482 // Public Variables with Default Settings
4483 //------------------------------------------------------------
4484
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"
4498 ;
4499
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);
4505
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');
4510
4511 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4512
4513 var series = g.selectAll('.nv-series')
4514 .data(function(d) {
4515 if(vers != 'furious') return d;
4516
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')
4522
4523 var seriesShape;
4524
4525 if(vers == 'classic') {
4526 seriesEnter.append('circle')
4527 .style('stroke-width', 2)
4528 .attr('class','nv-legend-symbol')
4529 .attr('r', 5);
4530
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);
4538
4539 seriesShape = series.select('rect');
4540
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)');
4545
4546 var seriesCheckbox = series.select('.nv-check-box');
4547
4548 seriesCheckbox.each(function(d,i) {
4549 d3.select(this).selectAll('path')
4550 .attr('stroke', setTextColor(d,i));
4551 });
4552 }
4553
4554 seriesEnter.append('text')
4555 .attr('text-anchor', 'start')
4556 .attr('class','nv-legend-text')
4557 .attr('dy', '.32em')
4558 .attr('dx', '8');
4559
4560 var seriesText = series.select('text.nv-legend-text');
4561
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;
4580 }
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});
4587 }
4588 }
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 });
4604 }
4605 }
4606 }
4607 dispatch.stateChange({
4608 disabled: data.map(function(d) { return !!d.disabled }),
4609 disengaged: data.map(function(d) { return !!d.disengaged })
4610 });
4611
4612 }
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 });
4631 }
4632 });
4633
4634 series.classed('nv-disabled', function(d) { return d.userDisabled });
4635 series.exit().remove();
4636
4637 seriesText
4638 .attr('fill', setTextColor)
4639 .text(getKey);
4640
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
4643
4644 var versPadding;
4645 switch(vers) {
4646 case 'furious' :
4647 versPadding = 23;
4648 break;
4649 case 'classic' :
4650 versPadding = 20;
4651 }
4652
4653 if (align) {
4654
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();
4663 }
4664 catch(e) {
4665 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4666 }
4667
4668 seriesWidths.push(nodeTextLength + padding);
4669 });
4670
4671 var seriesPerRow = 0;
4672 var legendWidth = 0;
4673 var columnWidths = [];
4674
4675 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4676 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4677 legendWidth += seriesWidths[seriesPerRow++];
4678 }
4679 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4680
4681 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4682 columnWidths = [];
4683 seriesPerRow--;
4684
4685 for (var k = 0; k < seriesWidths.length; k++) {
4686 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4687 columnWidths[k % seriesPerRow] = seriesWidths[k];
4688 }
4689
4690 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4691 return prev + cur;
4692 });
4693 }
4694
4695 var xPositions = [];
4696 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4697 xPositions[i] = curX;
4698 curX += columnWidths[i];
4699 }
4700
4701 series
4702 .attr('transform', function(d, i) {
4703 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
4704 });
4705
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 + ')');
4709 }
4710 else {
4711 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4712 }
4713
4714 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
4715
4716 } else {
4717
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;
4726
4727 if (width < margin.left + margin.right + xpos + length) {
4728 newxpos = xpos = 5;
4729 ypos += versPadding;
4730 }
4731
4732 newxpos += length;
4733 if (newxpos > maxwidth) maxwidth = newxpos;
4734
4735 return 'translate(' + xpos + ',' + ypos + ')';
4736 });
4737
4738 //position legend as far right as possible within the total width
4739 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4740
4741 height = margin.top + margin.bottom + ypos + 15;
4742 }
4743
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)
4753 }
4754
4755 seriesShape
4756 .style('fill', setBGColor)
4757 .style('stroke', function(d,i) { return d.color || color(d, i) });
4758 });
4759
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';
4766 }
4767 }
4768
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);
4774 }
4775 }
4776
4777 return chart;
4778 }
4779
4780 //============================================================
4781 // Expose Public Variables
4782 //------------------------------------------------------------
4783
4784 chart.dispatch = dispatch;
4785 chart.options = nv.utils.optionsFunc.bind(chart);
4786
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=_;}},
4799
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 });
4811
4812 nv.utils.initOptions(chart);
4813
4814 return chart;
4815 };
4816 //TODO: consider deprecating and using multibar with single series for this
4817 nv.models.historicalBar = function() {
4818 "use strict";
4819
4820 //============================================================
4821 // Public Variables with Default Settings
4822 //------------------------------------------------------------
4823
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
4844 ;
4845
4846 var renderWatch = nv.utils.renderWatch(dispatch, 0);
4847
4848 function chart(selection) {
4849 selection.each(function(data) {
4850 renderWatch.reset();
4851
4852 container = d3.select(this);
4853 var availableWidth = nv.utils.availableWidth(width, container, margin),
4854 availableHeight = nv.utils.availableHeight(height, container, margin);
4855
4856 nv.utils.initSVG(container);
4857
4858 // Setup Scales
4859 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
4860
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]);
4865
4866 y.domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
4867 .range(yRange || [availableHeight, 0]);
4868
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]);
4874
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]);
4879
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');
4886
4887 gEnter.append('g').attr('class', 'nv-bars');
4888 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4889
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 });
4899
4900 defsEnter.append('clipPath')
4901 .attr('id', 'nv-chart-clip-path-' + id)
4902 .append('rect');
4903
4904 wrap.select('#nv-chart-clip-path-' + id + ' rect')
4905 .attr('width', availableWidth)
4906 .attr('height', availableHeight);
4907
4908 g.attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
4909
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();
4913
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 });
4927
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 });
4964
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 );
4972
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)) });
4983
4984 });
4985
4986 renderWatch.renderEnd('historicalBar immediate');
4987 return chart;
4988 }
4989
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)
4995 ;
4996 };
4997
4998 chart.clearHighlights = function() {
4999 container
5000 .select(".nv-bars .nv-bar.hover")
5001 .classed("hover", false)
5002 ;
5003 };
5004
5005 //============================================================
5006 // Expose Public Variables
5007 //------------------------------------------------------------
5008
5009 chart.dispatch = dispatch;
5010 chart.options = nv.utils.optionsFunc.bind(chart);
5011
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=_;}},
5030
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 });
5042
5043 nv.utils.initOptions(chart);
5044
5045 return chart;
5046 };
5047
5048 nv.models.historicalBarChart = function(bar_model) {
5049 "use strict";
5050
5051 //============================================================
5052 // Public Variables with Default Settings
5053 //------------------------------------------------------------
5054
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()
5061 ;
5062
5063
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
5080 ;
5081
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 });
5093
5094
5095 //============================================================
5096 // Private Variables
5097 //------------------------------------------------------------
5098
5099 var renderWatch = nv.utils.renderWatch(dispatch, 0);
5100
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);
5107
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);
5113
5114 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5115 chart.container = this;
5116
5117 //set state.disabled
5118 state.disabled = data.map(function(d) { return !!d.disabled });
5119
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];
5128 }
5129 }
5130
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();
5137 }
5138
5139 // Setup Scales
5140 x = bars.xScale();
5141 y = bars.yScale();
5142
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');
5147
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');
5153
5154 // Legend
5155 if (showLegend) {
5156 legend.width(availableWidth);
5157
5158 g.select('.nv-legendWrap')
5159 .datum(data)
5160 .call(legend);
5161
5162 if ( margin.top != legend.height()) {
5163 margin.top = legend.height();
5164 availableHeight = nv.utils.availableHeight(height, container, margin);
5165 }
5166
5167 wrap.select('.nv-legendWrap')
5168 .attr('transform', 'translate(0,' + (-margin.top) +')')
5169 }
5170 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5171
5172 if (rightAlignYAxis) {
5173 g.select(".nv-y.nv-axis")
5174 .attr("transform", "translate(" + availableWidth + ",0)");
5175 }
5176
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);
5186 }
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 }));
5193
5194 var barsWrap = g.select('.nv-barsWrap')
5195 .datum(data.filter(function(d) { return !d.disabled }));
5196 barsWrap.transition().call(bars);
5197
5198 // Setup Axes
5199 if (showXAxis) {
5200 xAxis
5201 .scale(x)
5202 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
5203 .tickSize(-availableHeight, 0);
5204
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);
5210 }
5211
5212 if (showYAxis) {
5213 yAxis
5214 .scale(y)
5215 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
5216 .tickSize( -availableWidth, 0);
5217
5218 g.select('.nv-y.nv-axis')
5219 .transition()
5220 .call(yAxis);
5221 }
5222
5223 //============================================================
5224 // Event Handling/Dispatching (in chart's scope)
5225 //------------------------------------------------------------
5226
5227 interactiveLayer.dispatch.on('elementMousemove', function(e) {
5228 bars.clearHighlights();
5229
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 });
5250
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 })();
5263
5264 interactiveLayer.renderGuideLine(pointXLocation);
5265
5266 });
5267
5268 interactiveLayer.dispatch.on("elementMouseout",function(e) {
5269 dispatch.tooltipHide();
5270 bars.clearHighlights();
5271 });
5272
5273 legend.dispatch.on('legendClick', function(d,i) {
5274 d.disabled = !d.disabled;
5275
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 });
5282 }
5283
5284 state.disabled = data.map(function(d) { return !!d.disabled });
5285 dispatch.stateChange(state);
5286
5287 selection.transition().call(chart);
5288 });
5289
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;
5296
5297 state.disabled = data.map(function(d) { return !!d.disabled });
5298 dispatch.stateChange(state);
5299 chart.update();
5300 });
5301
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 });
5307
5308 state.disabled = e.disabled;
5309 }
5310
5311 chart.update();
5312 });
5313 });
5314
5315 renderWatch.renderEnd('historicalBarChart immediate');
5316 return chart;
5317 }
5318
5319 //============================================================
5320 // Event Handling/Dispatching (out of chart's scope)
5321 //------------------------------------------------------------
5322
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 });
5331
5332 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
5333 tooltip.hidden(true);
5334 });
5335
5336 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
5337 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
5338 });
5339
5340 //============================================================
5341 // Expose Public Variables
5342 //------------------------------------------------------------
5343
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;
5352
5353 chart.options = nv.utils.optionsFunc.bind(chart);
5354
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=_;}},
5364
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 }},
5376
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);
5403 }
5404 }}
5405 });
5406
5407 nv.utils.inheritOptions(chart, bars);
5408 nv.utils.initOptions(chart);
5409
5410 return chart;
5411 };
5412
5413
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());
5417
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 };
5436
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());
5440
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";
5461
5462 //============================================================
5463 // Public Variables with Default Settings
5464 //------------------------------------------------------------
5465
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"
5479 ;
5480
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);
5486
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');
5491
5492 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5493
5494 var series = g.selectAll('.nv-series')
5495 .data(function(d) {
5496 if(vers != 'furious') return d;
5497
5498 return d.filter(function(n) {
5499 return expanded ? true : !n.disengaged;
5500 });
5501 });
5502
5503 var seriesEnter = series.enter().append('g').attr('class', 'nv-series');
5504 var seriesShape;
5505
5506 var versPadding;
5507 switch(vers) {
5508 case 'furious' :
5509 versPadding = 23;
5510 break;
5511 case 'classic' :
5512 versPadding = 20;
5513 }
5514
5515 if(vers == 'classic') {
5516 seriesEnter.append('circle')
5517 .style('stroke-width', 2)
5518 .attr('class','nv-legend-symbol')
5519 .attr('r', 5);
5520
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);
5528
5529 seriesShape = series.select('.nv-legend-symbol');
5530
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)');
5535
5536 var seriesCheckbox = series.select('.nv-check-box');
5537
5538 seriesCheckbox.each(function(d,i) {
5539 d3.select(this).selectAll('path')
5540 .attr('stroke', setTextColor(d,i));
5541 });
5542 }
5543
5544 seriesEnter.append('text')
5545 .attr('text-anchor', 'start')
5546 .attr('class','nv-legend-text')
5547 .attr('dy', '.32em')
5548 .attr('dx', '8');
5549
5550 var seriesText = series.select('text.nv-legend-text');
5551
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;
5570 }
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});
5577 }
5578 }
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 });
5594 }
5595 }
5596 }
5597 dispatch.stateChange({
5598 disabled: data.map(function(d) { return !!d.disabled }),
5599 disengaged: data.map(function(d) { return !!d.disengaged })
5600 });
5601
5602 }
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 });
5621 }
5622 });
5623
5624 series.classed('nv-disabled', function(d) { return d.userDisabled });
5625 series.exit().remove();
5626
5627 seriesText
5628 .attr('fill', setTextColor)
5629 .text(getKey);
5630
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) {
5635
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();
5644 }
5645 catch(e) {
5646 nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
5647 }
5648
5649 seriesWidths.push(nodeTextLength + padding);
5650 });
5651
5652 var seriesPerRow = 0;
5653 var columnWidths = [];
5654 legendWidth = 0;
5655
5656 while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
5657 columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
5658 legendWidth += seriesWidths[seriesPerRow++];
5659 }
5660 if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
5661
5662 while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
5663 columnWidths = [];
5664 seriesPerRow--;
5665
5666 for (var k = 0; k < seriesWidths.length; k++) {
5667 if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
5668 columnWidths[k % seriesPerRow] = seriesWidths[k];
5669 }
5670
5671 legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
5672 return prev + cur;
5673 });
5674 }
5675
5676 var xPositions = [];
5677 for (var i = 0, curX = 0; i < seriesPerRow; i++) {
5678 xPositions[i] = curX;
5679 curX += columnWidths[i];
5680 }
5681
5682 series
5683 .attr('transform', function(d, i) {
5684 return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * versPadding) + ')';
5685 });
5686
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 + ')');
5690 }
5691 else {
5692 g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5693 }
5694
5695 height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * versPadding);
5696
5697 } else {
5698
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;
5707
5708 if (width < margin.left + margin.right + xpos + length) {
5709 newxpos = xpos = 5;
5710 ypos += versPadding;
5711 }
5712
5713 newxpos += length;
5714 if (newxpos > maxwidth) maxwidth = newxpos;
5715
5716 if(legendWidth < xpos + maxwidth) {
5717 legendWidth = xpos + maxwidth;
5718 }
5719 return 'translate(' + xpos + ',' + ypos + ')';
5720 });
5721
5722 //position legend as far right as possible within the total width
5723 g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
5724
5725 height = margin.top + margin.bottom + ypos + 15;
5726 }
5727
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);
5737
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);
5744
5745 var seriesBG = g.select('.nv-legend-bg');
5746
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);
5754
5755
5756 }
5757
5758 seriesShape
5759 .style('fill', setBGColor)
5760 .style('fill-opacity', setBGOpacity)
5761 .style('stroke', setBGColor);
5762 });
5763
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';
5771 }
5772 }
5773
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);
5779 }
5780 }
5781
5782
5783 function setBGOpacity(d,i) {
5784 if(expanded && vers == 'furious') {
5785 return 1;
5786 } else {
5787 return !!d.disabled ? 0 : 1;
5788 }
5789 }
5790
5791 return chart;
5792 }
5793
5794 //============================================================
5795 // Expose Public Variables
5796 //------------------------------------------------------------
5797
5798 chart.dispatch = dispatch;
5799 chart.options = nv.utils.optionsFunc.bind(chart);
5800
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=_;}},
5813
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 });
5825
5826 nv.utils.initOptions(chart);
5827
5828 return chart;
5829 };
5830
5831 nv.models.line = function() {
5832 "use strict";
5833 //============================================================
5834 // Public Variables with Default Settings
5835 //------------------------------------------------------------
5836
5837 var scatter = nv.models.scatter()
5838 ;
5839
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')
5856 ;
5857
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
5861 ;
5862
5863 //============================================================
5864
5865
5866 //============================================================
5867 // Private Variables
5868 //------------------------------------------------------------
5869
5870 var x0, y0 //used to store previous scales
5871 , renderWatch = nv.utils.renderWatch(dispatch, duration)
5872 ;
5873
5874 //============================================================
5875
5876
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);
5885
5886 // Setup Scales
5887 x = scatter.xScale();
5888 y = scatter.yScale();
5889
5890 x0 = x0 || x;
5891 y0 = y0 || y;
5892
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');
5899
5900 gEnter.append('g').attr('class', 'nv-groups');
5901 gEnter.append('g').attr('class', 'nv-scatterWrap');
5902
5903 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5904
5905 scatter
5906 .width(availableWidth)
5907 .height(availableHeight);
5908
5909 var scatterWrap = wrap.select('.nv-scatterWrap');
5910 scatterWrap.call(scatter);
5911
5912 defsEnter.append('clipPath')
5913 .attr('id', 'nv-edge-clip-' + scatter.id())
5914 .append('rect');
5915
5916 wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5917 .attr('width', availableWidth)
5918 .attr('height', (availableHeight > 0) ? availableHeight : 0);
5919
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() + ')' : '');
5923
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);
5930
5931 groups.exit().remove();
5932
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});
5943
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();
5960
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 });
5972
5973 var linePaths = groups.selectAll('path.nv-line')
5974 .data(function(d) { return [d.values] });
5975
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 );
5985
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 );
5994
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;
6001 }
6002
6003
6004 //============================================================
6005 // Expose Public Variables
6006 //------------------------------------------------------------
6007
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); });
6014
6015 chart.options = nv.utils.optionsFunc.bind(chart);
6016
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=_;}},
6024
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 });
6053
6054 nv.utils.inheritOptions(chart, scatter);
6055 nv.utils.initOptions(chart);
6056
6057 return chart;
6058 };
6059 nv.models.lineChart = function() {
6060 "use strict";
6061
6062 //============================================================
6063 // Public Variables with Default Settings
6064 //------------------------------------------------------------
6065
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()
6072 ;
6073
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
6090 ;
6091
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 });
6100
6101
6102 //============================================================
6103 // Private Variables
6104 //------------------------------------------------------------
6105
6106 var renderWatch = nv.utils.renderWatch(dispatch, duration);
6107
6108 var stateGetter = function(data) {
6109 return function(){
6110 return {
6111 active: data.map(function(d) { return !d.disabled })
6112 };
6113 }
6114 };
6115
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 });
6122 }
6123 };
6124
6125 function chart(selection) {
6126 renderWatch.reset();
6127 renderWatch.models(lines);
6128 if (showXAxis) renderWatch.models(xAxis);
6129 if (showYAxis) renderWatch.models(yAxis);
6130
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);
6137
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;
6145
6146 state
6147 .setter(stateSetter(data), chart.update)
6148 .getter(stateGetter(data))
6149 .update();
6150
6151 // DEPRECATED set state.disableddisabled
6152 state.disabled = data.map(function(d) { return !!d.disabled });
6153
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];
6162 }
6163 }
6164
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();
6171 }
6172
6173
6174 // Setup Scales
6175 x = lines.xScale();
6176 y = lines.yScale();
6177
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');
6182
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');
6189
6190 g.select("rect")
6191 .attr("width",availableWidth)
6192 .attr("height",(availableHeight > 0) ? availableHeight : 0);
6193
6194 // Legend
6195 if (showLegend) {
6196 legend.width(availableWidth);
6197
6198 g.select('.nv-legendWrap')
6199 .datum(data)
6200 .call(legend);
6201
6202 if ( margin.top != legend.height()) {
6203 margin.top = legend.height();
6204 availableHeight = nv.utils.availableHeight(height, container, margin);
6205 }
6206
6207 wrap.select('.nv-legendWrap')
6208 .attr('transform', 'translate(0,' + (-margin.top) +')')
6209 }
6210
6211 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6212
6213 if (rightAlignYAxis) {
6214 g.select(".nv-y.nv-axis")
6215 .attr("transform", "translate(" + availableWidth + ",0)");
6216 }
6217
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);
6227 }
6228
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 }));
6235
6236
6237 var linesWrap = g.select('.nv-linesWrap')
6238 .datum(data.filter(function(d) { return !d.disabled }));
6239
6240 linesWrap.call(lines);
6241
6242 // Setup Axes
6243 if (showXAxis) {
6244 xAxis
6245 .scale(x)
6246 ._ticks(nv.utils.calcTicksX(availableWidth/100, data) )
6247 .tickSize(-availableHeight, 0);
6248
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);
6253 }
6254
6255 if (showYAxis) {
6256 yAxis
6257 .scale(y)
6258 ._ticks(nv.utils.calcTicksY(availableHeight/36, data) )
6259 .tickSize( -availableWidth, 0);
6260
6261 g.select('.nv-y.nv-axis')
6262 .call(yAxis);
6263 }
6264
6265 //============================================================
6266 // Event Handling/Dispatching (in chart's scope)
6267 //------------------------------------------------------------
6268
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 });
6275
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);
6290 }
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;
6308 }
6309
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 })();
6322
6323 interactiveLayer.renderGuideLine(pointXLocation);
6324
6325 });
6326
6327 interactiveLayer.dispatch.on('elementClick', function(e) {
6328 var pointXLocation, allData = [];
6329
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 });
6347
6348 lines.dispatch.elementClick(allData);
6349 });
6350
6351 interactiveLayer.dispatch.on("elementMouseout",function(e) {
6352 lines.clearHighlights();
6353 });
6354
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 });
6360
6361 state.disabled = e.disabled;
6362 }
6363
6364 chart.update();
6365 });
6366
6367 });
6368
6369 renderWatch.renderEnd('lineChart immediate');
6370 return chart;
6371 }
6372
6373 //============================================================
6374 // Event Handling/Dispatching (out of chart's scope)
6375 //------------------------------------------------------------
6376
6377 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
6378 tooltip.data(evt).position(evt.pos).hidden(false);
6379 });
6380
6381 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6382 tooltip.hidden(true)
6383 });
6384
6385 //============================================================
6386 // Expose Public Variables
6387 //------------------------------------------------------------
6388
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;
6397
6398 chart.dispatch = dispatch;
6399 chart.options = nv.utils.optionsFunc.bind(chart);
6400
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=_;}},
6410
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 }},
6422
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);
6451 }
6452 }}
6453 });
6454
6455 nv.utils.inheritOptions(chart, lines);
6456 nv.utils.initOptions(chart);
6457
6458 return chart;
6459 };
6460 nv.models.linePlusBarChart = function() {
6461 "use strict";
6462
6463 //============================================================
6464 // Public Variables with Default Settings
6465 //------------------------------------------------------------
6466
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()
6480 ;
6481
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)'
6509 ;
6510
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');
6519
6520 tooltip.headerEnabled(true).headerFormatter(function(d, i) {
6521 return xAxis.tickFormat()(d, i);
6522 });
6523
6524 //============================================================
6525 // Private Variables
6526 //------------------------------------------------------------
6527
6528 var stateGetter = function(data) {
6529 return function(){
6530 return {
6531 active: data.map(function(d) { return !d.disabled })
6532 };
6533 }
6534 };
6535
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 });
6542 }
6543 };
6544
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;
6554
6555 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6556 chart.container = this;
6557
6558 state
6559 .setter(stateSetter(data), chart.update)
6560 .getter(stateGetter(data))
6561 .update();
6562
6563 // DEPRECATED set state.disableddisabled
6564 state.disabled = data.map(function(d) { return !!d.disabled });
6565
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];
6574 }
6575 }
6576
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();
6583 }
6584
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
6588
6589 x = bars.xScale();
6590 x2 = x2Axis.scale();
6591 y1 = bars.yScale();
6592 y2 = lines.yScale();
6593 y3 = bars2.yScale();
6594 y4 = lines2.yScale();
6595
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 });
6603
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 });
6611
6612 x.range([0, availableWidth]);
6613
6614 x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6615 .range([0, availableWidth]);
6616
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');
6621
6622 gEnter.append('g').attr('class', 'nv-legendWrap');
6623
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');
6631
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');
6641
6642 //============================================================
6643 // Legend
6644 //------------------------------------------------------------
6645
6646 if (showLegend) {
6647 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
6648 var legendXPosition = legend.align() ? legendWidth : 0;
6649
6650 legend.width(legendWidth);
6651
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);
6659
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;
6664 }
6665
6666 g.select('.nv-legendWrap')
6667 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
6668 }
6669
6670 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6671
6672 //============================================================
6673 // Context chart (focus chart) components
6674 //------------------------------------------------------------
6675
6676 // hide or show the focus context chart
6677 g.select('.nv-context').style('display', focusEnable ? 'initial' : 'none');
6678
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 }));
6695
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 ]);
6704
6705 g.select('.nv-context')
6706 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')');
6707
6708 bars2Wrap.transition().call(bars2);
6709 lines2Wrap.transition().call(lines2);
6710
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);
6720 }
6721
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
6731
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)');
6738
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);
6743 }
6744
6745 // Setup Brush
6746 brush.x(x2).on('brush', onBrush);
6747
6748 if (brushExtent) brush.extent(brushExtent);
6749
6750 var brushBG = g.select('.nv-brushBackground').selectAll('g')
6751 .data([brushExtent || brush.extent()]);
6752
6753 var brushBGenter = brushBG.enter()
6754 .append('g');
6755
6756 brushBGenter.append('rect')
6757 .attr('class', 'left')
6758 .attr('x', 0)
6759 .attr('y', 0)
6760 .attr('height', availableHeight2);
6761
6762 brushBGenter.append('rect')
6763 .attr('class', 'right')
6764 .attr('x', 0)
6765 .attr('y', 0)
6766 .attr('height', availableHeight2);
6767
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);
6774
6775 //============================================================
6776 // Event Handling/Dispatching (in chart's scope)
6777 //------------------------------------------------------------
6778
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 });
6785
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;
6793 }
6794 chart.update();
6795 });
6796
6797 //============================================================
6798 // Functions
6799 //------------------------------------------------------------
6800
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);
6815 }
6816
6817
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);
6827
6828 d3.select(this).select('.right')
6829 .attr('x', x2(d[1]))
6830 .attr('width', rightWidth < 0 ? 0 : rightWidth);
6831 });
6832 }
6833
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();
6839
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 }));
6847
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 }));
6854
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 })
6864 }
6865 })
6866 );
6867
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 })
6879 }
6880 })
6881 );
6882
6883 // Update Main (Focus) X Axis
6884 if (dataBars.length) {
6885 x = bars.xScale();
6886 } else {
6887 x = lines.xScale();
6888 }
6889
6890 xAxis
6891 .scale(x)
6892 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
6893 .tickSize(-availableHeight1, 0);
6894
6895 xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
6896
6897 g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
6898 .call(xAxis);
6899
6900 // Update Main (Focus) Bars and Lines
6901 focusBarsWrap.transition().duration(transitionDuration).call(bars);
6902 focusLinesWrap.transition().duration(transitionDuration).call(lines);
6903
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] + ')');
6907
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
6916
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)');
6922
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);
6927 }
6928
6929 onBrush();
6930
6931 });
6932
6933 return chart;
6934 }
6935
6936 //============================================================
6937 // Event Handling/Dispatching (out of chart's scope)
6938 //------------------------------------------------------------
6939
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 });
6950
6951 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
6952 tooltip.hidden(true)
6953 });
6954
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 });
6969
6970 bars.dispatch.on('elementMouseout.tooltip', function(evt) {
6971 tooltip.hidden(true);
6972 });
6973
6974 bars.dispatch.on('elementMousemove.tooltip', function(evt) {
6975 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
6976 });
6977
6978 //============================================================
6979
6980
6981 //============================================================
6982 // Expose Public Variables
6983 //------------------------------------------------------------
6984
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;
6999
7000 chart.options = nv.utils.optionsFunc.bind(chart);
7001
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=_;}},
7015
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 }},
7027
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 });
7057
7058 nv.utils.inheritOptions(chart, lines);
7059 nv.utils.initOptions(chart);
7060
7061 return chart;
7062 };
7063 nv.models.lineWithFocusChart = function() {
7064 "use strict";
7065
7066 //============================================================
7067 // Public Variables with Default Settings
7068 //------------------------------------------------------------
7069
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()
7080 ;
7081
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
7100 ;
7101
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');
7108
7109 tooltip.valueFormatter(function(d, i) {
7110 return yAxis.tickFormat()(d, i);
7111 }).headerFormatter(function(d, i) {
7112 return xAxis.tickFormat()(d, i);
7113 });
7114
7115 //============================================================
7116 // Private Variables
7117 //------------------------------------------------------------
7118
7119 var stateGetter = function(data) {
7120 return function(){
7121 return {
7122 active: data.map(function(d) { return !d.disabled })
7123 };
7124 }
7125 };
7126
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 });
7133 }
7134 };
7135
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;
7144
7145 chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7146 chart.container = this;
7147
7148 state
7149 .setter(stateSetter(data), chart.update)
7150 .getter(stateGetter(data))
7151 .update();
7152
7153 // DEPRECATED set state.disableddisabled
7154 state.disabled = data.map(function(d) { return !!d.disabled });
7155
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];
7164 }
7165 }
7166
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();
7173 }
7174
7175 // Setup Scales
7176 x = lines.xScale();
7177 y = lines.yScale();
7178 x2 = lines2.xScale();
7179 y2 = lines2.yScale();
7180
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');
7185
7186 gEnter.append('g').attr('class', 'nv-legendWrap');
7187
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');
7193
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');
7200
7201 // Legend
7202 if (showLegend) {
7203 legend.width(availableWidth);
7204
7205 g.select('.nv-legendWrap')
7206 .datum(data)
7207 .call(legend);
7208
7209 if ( margin.top != legend.height()) {
7210 margin.top = legend.height();
7211 availableHeight1 = nv.utils.availableHeight(height, container, margin) - height2;
7212 }
7213
7214 g.select('.nv-legendWrap')
7215 .attr('transform', 'translate(0,' + (-margin.top) +')')
7216 }
7217
7218 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7219
7220
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);
7230 }
7231
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 );
7245
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 );
7259
7260 g.select('.nv-context')
7261 .attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7262
7263 var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
7264 .datum(data.filter(function(d) { return !d.disabled }))
7265
7266 d3.transition(contextLinesWrap).call(lines2);
7267
7268 // Setup Main (Focus) Axes
7269 xAxis
7270 .scale(x)
7271 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7272 .tickSize(-availableHeight1, 0);
7273
7274 yAxis
7275 .scale(y)
7276 ._ticks( nv.utils.calcTicksY(availableHeight1/36, data) )
7277 .tickSize( -availableWidth, 0);
7278
7279 g.select('.nv-focus .nv-x.nv-axis')
7280 .attr('transform', 'translate(0,' + availableHeight1 + ')');
7281
7282 // Setup Brush
7283 brush
7284 .x(x2)
7285 .on('brush', function() {
7286 onBrush();
7287 });
7288
7289 if (brushExtent) brush.extent(brushExtent);
7290
7291 var brushBG = g.select('.nv-brushBackground').selectAll('g')
7292 .data([brushExtent || brush.extent()])
7293
7294 var brushBGenter = brushBG.enter()
7295 .append('g');
7296
7297 brushBGenter.append('rect')
7298 .attr('class', 'left')
7299 .attr('x', 0)
7300 .attr('y', 0)
7301 .attr('height', availableHeight2);
7302
7303 brushBGenter.append('rect')
7304 .attr('class', 'right')
7305 .attr('x', 0)
7306 .attr('y', 0)
7307 .attr('height', availableHeight2);
7308
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);
7314
7315 onBrush();
7316
7317 // Setup Secondary (Context) Axes
7318 x2Axis
7319 .scale(x2)
7320 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
7321 .tickSize(-availableHeight2, 0);
7322
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);
7327
7328 y2Axis
7329 .scale(y2)
7330 ._ticks( nv.utils.calcTicksY(availableHeight2/36, data) )
7331 .tickSize( -availableWidth, 0);
7332
7333 d3.transition(g.select('.nv-context .nv-y.nv-axis'))
7334 .call(y2Axis);
7335
7336 g.select('.nv-context .nv-x.nv-axis')
7337 .attr('transform', 'translate(0,' + y2.range()[0] + ')');
7338
7339 //============================================================
7340 // Event Handling/Dispatching (in chart's scope)
7341 //------------------------------------------------------------
7342
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 });
7349
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 });
7363
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);
7369 }
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;
7387 }
7388
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 })();
7401
7402 interactiveLayer.renderGuideLine(pointXLocation);
7403
7404 });
7405
7406 interactiveLayer.dispatch.on("elementMouseout",function(e) {
7407 lines.clearHighlights();
7408 });
7409
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 });
7415 }
7416 chart.update();
7417 });
7418
7419 //============================================================
7420 // Functions
7421 //------------------------------------------------------------
7422
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);
7437 }
7438
7439
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);
7449
7450 d3.select(this).select('.right')
7451 .attr('x', x2(d[1]))
7452 .attr('width', rightWidth < 0 ? 0 : rightWidth);
7453 });
7454 }
7455
7456
7457 function onBrush() {
7458 brushExtent = brush.empty() ? null : brush.extent();
7459 var extent = brush.empty() ? x2.domain() : brush.extent();
7460
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;
7464 }
7465
7466 dispatch.brush({extent: extent, brush: brush});
7467
7468
7469 updateBrushBG();
7470
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 })
7483 }
7484 })
7485 );
7486 focusLinesWrap.transition().duration(transitionDuration).call(lines);
7487
7488
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);
7494 }
7495 });
7496
7497 return chart;
7498 }
7499
7500 //============================================================
7501 // Event Handling/Dispatching (out of chart's scope)
7502 //------------------------------------------------------------
7503
7504 lines.dispatch.on('elementMouseover.tooltip', function(evt) {
7505 tooltip.data(evt).position(evt.pos).hidden(false);
7506 });
7507
7508 lines.dispatch.on('elementMouseout.tooltip', function(evt) {
7509 tooltip.hidden(true)
7510 });
7511
7512 //============================================================
7513 // Expose Public Variables
7514 //------------------------------------------------------------
7515
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;
7527
7528 chart.options = nv.utils.optionsFunc.bind(chart);
7529
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=_;}},
7539
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 }},
7551
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);
7596 }
7597 }}
7598 });
7599
7600 nv.utils.inheritOptions(chart, lines);
7601 nv.utils.initOptions(chart);
7602
7603 return chart;
7604 };
7605
7606 nv.models.multiBar = function() {
7607 "use strict";
7608
7609 //============================================================
7610 // Public Variables with Default Settings
7611 //------------------------------------------------------------
7612
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')
7637 ;
7638
7639 //============================================================
7640 // Private Variables
7641 //------------------------------------------------------------
7642
7643 var x0, y0 //used to store previous scales
7644 , renderWatch = nv.utils.renderWatch(dispatch, duration)
7645 ;
7646
7647 var last_datalength = 0;
7648
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;
7654
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 };
7664
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 )}];
7674
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);
7681
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 });
7694 }
7695 }
7696 });
7697 data = parsed;
7698 }
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 });
7706
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
7719 {
7720 f.y1 = f.size + posBase;
7721 posBase = posBase + f.size;
7722 }
7723 }
7724
7725 });
7726 });
7727 }
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 });
7736
7737 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7738 .rangeBands(xRange || [0, availableWidth], groupSpacing);
7739
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
7748 }
7749 }
7750 return domain;
7751 }).concat(forceY)))
7752 .range(yRange || [availableHeight, 0]);
7753
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]);
7759
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]);
7764
7765 x0 = x0 || x;
7766 y0 = y0 || y;
7767
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');
7774
7775 gEnter.append('g').attr('class', 'nv-groups');
7776 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7777
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);
7784
7785 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7786
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);
7792
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);
7800 }
7801 }
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);
7819
7820 var bars = groups.selectAll('rect.nv-bar')
7821 .data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7822 bars.exit().remove();
7823
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)'; })
7833 ;
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)'; })
7879
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(); });
7885 }
7886
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;
7907 }
7908 }
7909 }
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;
7917 }
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);
7925 }
7926 }
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);
7939 }
7940 return width;
7941 }
7942 });
7943 }
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 });
7960 }
7961
7962 //store old scales for use in transitions on update
7963 x0 = x.copy();
7964 y0 = y.copy();
7965
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;
7969 }
7970
7971 });
7972
7973 renderWatch.renderEnd('multibar immediate');
7974
7975 return chart;
7976 }
7977
7978 //============================================================
7979 // Expose Public Variables
7980 //------------------------------------------------------------
7981
7982 chart.dispatch = dispatch;
7983
7984 chart.options = nv.utils.optionsFunc.bind(chart);
7985
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=_;}},
8006
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 });
8025
8026 nv.utils.initOptions(chart);
8027
8028 return chart;
8029 };
8030 nv.models.multiBarChart = function() {
8031 "use strict";
8032
8033 //============================================================
8034 // Public Variables with Default Settings
8035 //------------------------------------------------------------
8036
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()
8043 ;
8044
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
8066 ;
8067
8068 state.stacked = false // DEPRECATED Maintained for backward compatibility
8069
8070 multibar.stacked(false);
8071 xAxis
8072 .orient('bottom')
8073 .tickPadding(7)
8074 .showMaxMin(false)
8075 .tickFormat(function(d) { return d })
8076 ;
8077 yAxis
8078 .orient((rightAlignYAxis) ? 'right' : 'left')
8079 .tickFormat(d3.format(',.1f'))
8080 ;
8081
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 });
8090
8091 controls.updateState(false);
8092
8093 //============================================================
8094 // Private Variables
8095 //------------------------------------------------------------
8096
8097 var renderWatch = nv.utils.renderWatch(dispatch);
8098 var stacked = false;
8099
8100 var stateGetter = function(data) {
8101 return function(){
8102 return {
8103 active: data.map(function(d) { return !d.disabled }),
8104 stacked: stacked
8105 };
8106 }
8107 };
8108
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 });
8117 }
8118 };
8119
8120 function chart(selection) {
8121 renderWatch.reset();
8122 renderWatch.models(multibar);
8123 if (showXAxis) renderWatch.models(xAxis);
8124 if (showYAxis) renderWatch.models(yAxis);
8125
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);
8132
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;
8142
8143 state
8144 .setter(stateSetter(data), chart.update)
8145 .getter(stateGetter(data))
8146 .update();
8147
8148 // DEPRECATED set state.disableddisabled
8149 state.disabled = data.map(function(d) { return !!d.disabled });
8150
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];
8159 }
8160 }
8161
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();
8168 }
8169
8170 // Setup Scales
8171 x = multibar.xScale();
8172 y = multibar.yScale();
8173
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');
8178
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');
8184
8185 // Legend
8186 if (showLegend) {
8187 legend.width(availableWidth - controlWidth());
8188
8189 g.select('.nv-legendWrap')
8190 .datum(data)
8191 .call(legend);
8192
8193 if ( margin.top != legend.height()) {
8194 margin.top = legend.height();
8195 availableHeight = nv.utils.availableHeight(height, container, margin);
8196 }
8197
8198 g.select('.nv-legendWrap')
8199 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8200 }
8201
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 ];
8208
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);
8214 }
8215
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)");
8220 }
8221
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 }));
8230
8231
8232 var barsWrap = g.select('.nv-barsWrap')
8233 .datum(data.filter(function(d) { return !d.disabled }));
8234
8235 barsWrap.call(multibar);
8236
8237 // Setup Axes
8238 if (showXAxis) {
8239 xAxis
8240 .scale(x)
8241 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
8242 .tickSize(-availableHeight, 0);
8243
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);
8248
8249 var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8250
8251 xTicks
8252 .selectAll('line, text')
8253 .style('opacity', 1)
8254
8255 if (staggerLabels) {
8256 var getTranslate = function(x,y) {
8257 return "translate(" + x + "," + y + ")";
8258 };
8259
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 });
8267
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 });
8273 }
8274
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);
8282
8283 if(rotateLabels)
8284 xTicks
8285 .selectAll('.tick text')
8286 .attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8287 .style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8288
8289 g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8290 .style('opacity', 1);
8291 }
8292
8293 if (showYAxis) {
8294 yAxis
8295 .scale(y)
8296 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
8297 .tickSize( -availableWidth, 0);
8298
8299 g.select('.nv-y.nv-axis')
8300 .call(yAxis);
8301 }
8302
8303 //============================================================
8304 // Event Handling/Dispatching (in chart's scope)
8305 //------------------------------------------------------------
8306
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 });
8313
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;
8321
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;
8331 }
8332
8333 state.stacked = multibar.stacked();
8334 dispatch.stateChange(state);
8335 chart.update();
8336 });
8337
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;
8345 }
8346 if (typeof e.stacked !== 'undefined') {
8347 multibar.stacked(e.stacked);
8348 state.stacked = e.stacked;
8349 stacked = e.stacked;
8350 }
8351 chart.update();
8352 });
8353 });
8354
8355 renderWatch.renderEnd('multibarchart immediate');
8356 return chart;
8357 }
8358
8359 //============================================================
8360 // Event Handling/Dispatching (out of chart's scope)
8361 //------------------------------------------------------------
8362
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 });
8372
8373 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
8374 tooltip.hidden(true);
8375 });
8376
8377 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
8378 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
8379 });
8380
8381 //============================================================
8382 // Expose Public Variables
8383 //------------------------------------------------------------
8384
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;
8394
8395 chart.options = nv.utils.optionsFunc.bind(chart);
8396
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=_;}},
8411
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 }},
8423
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 });
8451
8452 nv.utils.inheritOptions(chart, multibar);
8453 nv.utils.initOptions(chart);
8454
8455 return chart;
8456 };
8457
8458 nv.models.multiBarHorizontal = function() {
8459 "use strict";
8460
8461 //============================================================
8462 // Public Variables with Default Settings
8463 //------------------------------------------------------------
8464
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')
8492 ;
8493
8494 //============================================================
8495 // Private Variables
8496 //------------------------------------------------------------
8497
8498 var x0, y0; //used to store previous scales
8499 var renderWatch = nv.utils.renderWatch(dispatch, duration);
8500
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;
8506
8507 container = d3.select(this);
8508 nv.utils.initSVG(container);
8509
8510 if (stacked)
8511 data = d3.layout.stack()
8512 .offset('zero')
8513 .values(function(d){ return d.values })
8514 .y(getY)
8515 (data);
8516
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 });
8524
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
8536 {
8537 f.y1 = posBase;
8538 posBase = posBase + f.size;
8539 }
8540 });
8541 });
8542
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 });
8551
8552 x.domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8553 .rangeBands(xRange || [0, availableHeight], groupSpacing);
8554
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)))
8556
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]);
8561
8562 x0 = x0 || x;
8563 y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8564
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');
8571
8572 gEnter.append('g').attr('class', 'nv-groups');
8573 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8574
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);
8592
8593 var bars = groups.selectAll('g.nv-bar')
8594 .data(function(d) { return d.values });
8595 bars.exit().remove();
8596
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 });
8601
8602 barsEnter.append('rect')
8603 .attr('width', 0)
8604 .attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8605
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 });
8653
8654 if (getYerr(data[0],0)) {
8655 barsEnter.append('polyline');
8656
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 });
8671 }
8672
8673 barsEnter.append('text');
8674
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('');
8694 }
8695
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 });
8706 }
8707 else {
8708 bars.selectAll('text.nv-bar-label').text('');
8709 }
8710
8711 bars
8712 .attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8713
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(); });
8719 }
8720
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
8739 +
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 });
8748
8749 //store old scales for use in transitions on update
8750 x0 = x.copy();
8751 y0 = y.copy();
8752
8753 });
8754
8755 renderWatch.renderEnd('multibarHorizontal immediate');
8756 return chart;
8757 }
8758
8759 //============================================================
8760 // Expose Public Variables
8761 //------------------------------------------------------------
8762
8763 chart.dispatch = dispatch;
8764
8765 chart.options = nv.utils.optionsFunc.bind(chart);
8766
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=_;}},
8790
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 });
8809
8810 nv.utils.initOptions(chart);
8811
8812 return chart;
8813 };
8814
8815 nv.models.multiBarHorizontalChart = function() {
8816 "use strict";
8817
8818 //============================================================
8819 // Public Variables with Default Settings
8820 //------------------------------------------------------------
8821
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()
8828 ;
8829
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
8848 ;
8849
8850 state.stacked = false; // DEPRECATED Maintained for backward compatibility
8851
8852 multibar.stacked(stacked);
8853
8854 xAxis
8855 .orient('left')
8856 .tickPadding(5)
8857 .showMaxMin(false)
8858 .tickFormat(function(d) { return d })
8859 ;
8860 yAxis
8861 .orient('bottom')
8862 .tickFormat(d3.format(',.1f'))
8863 ;
8864
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 });
8873
8874 controls.updateState(false);
8875
8876 //============================================================
8877 // Private Variables
8878 //------------------------------------------------------------
8879
8880 var stateGetter = function(data) {
8881 return function(){
8882 return {
8883 active: data.map(function(d) { return !d.disabled }),
8884 stacked: stacked
8885 };
8886 }
8887 };
8888
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 });
8897 }
8898 };
8899
8900 var renderWatch = nv.utils.renderWatch(dispatch, duration);
8901
8902 function chart(selection) {
8903 renderWatch.reset();
8904 renderWatch.models(multibar);
8905 if (showXAxis) renderWatch.models(xAxis);
8906 if (showYAxis) renderWatch.models(yAxis);
8907
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);
8914
8915 chart.update = function() { container.transition().duration(duration).call(chart) };
8916 chart.container = this;
8917
8918 stacked = multibar.stacked();
8919
8920 state
8921 .setter(stateSetter(data), chart.update)
8922 .getter(stateGetter(data))
8923 .update();
8924
8925 // DEPRECATED set state.disableddisabled
8926 state.disabled = data.map(function(d) { return !!d.disabled });
8927
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];
8936 }
8937 }
8938
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();
8945 }
8946
8947 // Setup Scales
8948 x = multibar.xScale();
8949 y = multibar.yScale();
8950
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');
8955
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');
8963
8964 // Legend
8965 if (showLegend) {
8966 legend.width(availableWidth - controlWidth());
8967
8968 g.select('.nv-legendWrap')
8969 .datum(data)
8970 .call(legend);
8971
8972 if ( margin.top != legend.height()) {
8973 margin.top = legend.height();
8974 availableHeight = nv.utils.availableHeight(height, container, margin);
8975 }
8976
8977 g.select('.nv-legendWrap')
8978 .attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8979 }
8980
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 ];
8987
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);
8993 }
8994
8995 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8996
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 }));
9005
9006 var barsWrap = g.select('.nv-barsWrap')
9007 .datum(data.filter(function(d) { return !d.disabled }));
9008
9009 barsWrap.transition().call(multibar);
9010
9011 // Setup Axes
9012 if (showXAxis) {
9013 xAxis
9014 .scale(x)
9015 ._ticks( nv.utils.calcTicksY(availableHeight/24, data) )
9016 .tickSize(-availableWidth, 0);
9017
9018 g.select('.nv-x.nv-axis').call(xAxis);
9019
9020 var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9021
9022 xTicks
9023 .selectAll('line, text');
9024 }
9025
9026 if (showYAxis) {
9027 yAxis
9028 .scale(y)
9029 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9030 .tickSize( -availableHeight, 0);
9031
9032 g.select('.nv-y.nv-axis')
9033 .attr('transform', 'translate(0,' + availableHeight + ')');
9034 g.select('.nv-y.nv-axis').call(yAxis);
9035 }
9036
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)
9043 ;
9044
9045 //============================================================
9046 // Event Handling/Dispatching (in chart's scope)
9047 //------------------------------------------------------------
9048
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 });
9055
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;
9063
9064 switch (d.key) {
9065 case 'Grouped':
9066 multibar.stacked(false);
9067 break;
9068 case 'Stacked':
9069 multibar.stacked(true);
9070 break;
9071 }
9072
9073 state.stacked = multibar.stacked();
9074 dispatch.stateChange(state);
9075 stacked = multibar.stacked();
9076
9077 chart.update();
9078 });
9079
9080 // Update chart from a state object passed to event handler
9081 dispatch.on('changeState', function(e) {
9082
9083 if (typeof e.disabled !== 'undefined') {
9084 data.forEach(function(series,i) {
9085 series.disabled = e.disabled[i];
9086 });
9087
9088 state.disabled = e.disabled;
9089 }
9090
9091 if (typeof e.stacked !== 'undefined') {
9092 multibar.stacked(e.stacked);
9093 state.stacked = e.stacked;
9094 stacked = e.stacked;
9095 }
9096
9097 chart.update();
9098 });
9099 });
9100 renderWatch.renderEnd('multibar horizontal chart immediate');
9101 return chart;
9102 }
9103
9104 //============================================================
9105 // Event Handling/Dispatching (out of chart's scope)
9106 //------------------------------------------------------------
9107
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 });
9117
9118 multibar.dispatch.on('elementMouseout.tooltip', function(evt) {
9119 tooltip.hidden(true);
9120 });
9121
9122 multibar.dispatch.on('elementMousemove.tooltip', function(evt) {
9123 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
9124 });
9125
9126 //============================================================
9127 // Expose Public Variables
9128 //------------------------------------------------------------
9129
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;
9139
9140 chart.options = nv.utils.optionsFunc.bind(chart);
9141
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=_;}},
9153
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 }},
9165
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 });
9189
9190 nv.utils.inheritOptions(chart, multibar);
9191 nv.utils.initOptions(chart);
9192
9193 return chart;
9194 };
9195 nv.models.multiChart = function() {
9196 "use strict";
9197
9198 //============================================================
9199 // Public Variables with Default Settings
9200 //------------------------------------------------------------
9201
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
9214 ;
9215
9216 //============================================================
9217 // Private Variables
9218 //------------------------------------------------------------
9219
9220 var x = d3.scale.linear(),
9221 yScale1 = d3.scale.linear(),
9222 yScale2 = d3.scale.linear(),
9223
9224 lines1 = nv.models.line().yScale(yScale1),
9225 lines2 = nv.models.line().yScale(yScale2),
9226
9227 bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9228 bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9229
9230 stack1 = nv.models.stackedArea().yScale(yScale1),
9231 stack2 = nv.models.stackedArea().yScale(yScale2),
9232
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'),
9236
9237 legend = nv.models.legend().height(30),
9238 tooltip = nv.models.tooltip(),
9239 dispatch = d3.dispatch();
9240
9241 function chart(selection) {
9242 selection.each(function(data) {
9243 var container = d3.select(this),
9244 that = this;
9245 nv.utils.initSVG(container);
9246
9247 chart.update = function() { container.transition().call(chart); };
9248 chart.container = this;
9249
9250 var availableWidth = nv.utils.availableWidth(width, container, margin),
9251 availableHeight = nv.utils.availableHeight(height, container, margin);
9252
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});
9259
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();
9266 }
9267
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 });
9274
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 });
9281
9282 x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9283 .range([0, availableWidth]);
9284
9285 var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9286 var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9287
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');
9298
9299 var g = wrap.select('g');
9300
9301 var color_array = data.map(function(d,i) {
9302 return data[i].color || color(d, i);
9303 });
9304
9305 if (showLegend) {
9306 var legendWidth = legend.align() ? availableWidth / 2 : availableWidth;
9307 var legendXPosition = legend.align() ? legendWidth : 0;
9308
9309 legend.width(legendWidth);
9310 legend.color(color_array);
9311
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);
9319
9320 if ( margin.top != legend.height()) {
9321 margin.top = legend.height();
9322 availableHeight = nv.utils.availableHeight(height, container, margin);
9323 }
9324
9325 g.select('.legendWrap')
9326 .attr('transform', 'translate(' + legendXPosition + ',' + (-margin.top) +')');
9327 }
9328
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'}));
9355
9356 g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9357
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}));
9370
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}]) : [];
9377
9378 yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9379 .range([0, availableHeight]);
9380
9381 yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9382 .range([0, availableHeight]);
9383
9384 lines1.yDomain(yScale1.domain());
9385 bars1.yDomain(yScale1.domain());
9386 stack1.yDomain(yScale1.domain());
9387
9388 lines2.yDomain(yScale2.domain());
9389 bars2.yDomain(yScale2.domain());
9390 stack2.yDomain(yScale2.domain());
9391
9392 if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9393 if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9394
9395 if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9396 if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9397
9398 if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9399 if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9400
9401 xAxis
9402 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
9403 .tickSize(-availableHeight, 0);
9404
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);
9409
9410 yAxis1
9411 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9412 .tickSize( -availableWidth, 0);
9413
9414
9415 d3.transition(g.select('.nv-y1.nv-axis'))
9416 .call(yAxis1);
9417
9418 yAxis2
9419 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
9420 .tickSize( -availableWidth, 0);
9421
9422 d3.transition(g.select('.nv-y2.nv-axis'))
9423 .call(yAxis2);
9424
9425 g.select('.nv-y1.nv-axis')
9426 .classed('nv-disabled', series1.length ? false : true)
9427 .attr('transform', 'translate(' + x.range()[0] + ',0)');
9428
9429 g.select('.nv-y2.nv-axis')
9430 .classed('nv-disabled', series2.length ? false : true)
9431 .attr('transform', 'translate(' + x.range()[1] + ',0)');
9432
9433 legend.dispatch.on('stateChange', function(newState) {
9434 chart.update();
9435 });
9436
9437 //============================================================
9438 // Event Handling/Dispatching
9439 //------------------------------------------------------------
9440
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);
9456 }
9457
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);
9470 }
9471
9472 function mouseover_bar(evt) {
9473 var yaxis = data[evt.data.series].yAxis === 2 ? yAxis2 : yAxis1;
9474
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);
9487 }
9488
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 });
9497
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 });
9506
9507 bars1.dispatch.on('elementMouseover.tooltip', mouseover_bar);
9508 bars2.dispatch.on('elementMouseover.tooltip', mouseover_bar);
9509
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 });
9522
9523 });
9524
9525 return chart;
9526 }
9527
9528 //============================================================
9529 // Global getters and setters
9530 //------------------------------------------------------------
9531
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;
9543
9544 chart.options = nv.utils.optionsFunc.bind(chart);
9545
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=_;}},
9555
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 }},
9567
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 });
9604
9605 nv.utils.initOptions(chart);
9606
9607 return chart;
9608 };
9609
9610
9611 nv.models.ohlcBar = function() {
9612 "use strict";
9613
9614 //============================================================
9615 // Public Variables with Default Settings
9616 //------------------------------------------------------------
9617
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')
9642 ;
9643
9644 //============================================================
9645 // Private Variables
9646 //------------------------------------------------------------
9647
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);
9653
9654 nv.utils.initSVG(container);
9655
9656 // ohlc bar width.
9657 var w = (availableWidth / data[0].values.length) * .9;
9658
9659 // Setup Scales
9660 x.domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9661
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]);
9666
9667 y.domain(yDomain || [
9668 d3.min(data[0].values.map(getLow).concat(forceY)),
9669 d3.max(data[0].values.map(getHigh).concat(forceY))
9670 ]
9671 ).range(yRange || [availableHeight, 0]);
9672
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]);
9678
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]);
9683
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');
9690
9691 gEnter.append('g').attr('class', 'nv-ticks');
9692
9693 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9694
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 });
9704
9705 defsEnter.append('clipPath')
9706 .attr('id', 'nv-chart-clip-path-' + id)
9707 .append('rect');
9708
9709 wrap.select('#nv-chart-clip-path-' + id + ' rect')
9710 .attr('width', availableWidth)
9711 .attr('height', availableHeight);
9712
9713 g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9714
9715 var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9716 .data(function(d) { return d });
9717 ticks.exit().remove();
9718
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)) });
9746
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 });
9751
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 });
9776
9777 return chart;
9778 }
9779
9780
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)
9786 ;
9787 };
9788
9789 chart.clearHighlights = function() {
9790 container.select(".nv-ohlcBar .nv-tick.hover")
9791 .classed("hover", false)
9792 ;
9793 };
9794
9795 //============================================================
9796 // Expose Public Variables
9797 //------------------------------------------------------------
9798
9799 chart.dispatch = dispatch;
9800 chart.options = nv.utils.optionsFunc.bind(chart);
9801
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=_;}},
9818
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=_;}},
9825
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 });
9837
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";
9845
9846 //============================================================
9847 // Public Variables with Default Settings
9848 //------------------------------------------------------------
9849
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')
9863 ;
9864
9865 //============================================================
9866 // Private Variables
9867 //------------------------------------------------------------
9868
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);
9874
9875 nv.utils.initSVG(container);
9876
9877 active = data; //set all active before first brush call
9878
9879 // Setup Scales
9880 x.rangePoints([0, availableWidth], 1).domain(dimensionNames);
9881
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;
9893 }
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;
9898 }
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]);
9904
9905 y[d].brush = d3.svg.brush().y(y[d]).on('brush', brush);
9906
9907 return d != 'name';
9908 });
9909
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');
9915
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');
9919
9920 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9921
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);
9928
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]; });
9941
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; });
9951
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);
9957
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 });
9971
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 });
9981
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');
9988
9989 dimensions.attr('transform', function(d) { return 'translate(' + x(d) + ',0)'; });
9990 dimensions.exit().remove();
9991
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);
10010
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 });
10015
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);
10023
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;
10032
10033 //If it's not already the case, allow brush to select undefined values
10034 if(axisWithMissingValues.indexOf(p) < 0) {
10035
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);
10039 }
10040
10041 return [x(p), y[p](min)];
10042 }
10043
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");
10051 }
10052
10053 return [x(p), y[p](d[p])];
10054 }));
10055 }
10056
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(); });
10061
10062 filters = []; //erase current filters
10063 actives.forEach(function(d,i) {
10064 filters[i] = {
10065 dimension: d,
10066 extent: extents[i]
10067 }
10068 });
10069
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 });
10079
10080 dispatch.brush({
10081 filters: filters,
10082 active: active
10083 });
10084 }
10085
10086 function dragStart(d, i) {
10087 dragging[d] = this.parentNode.__origin__ = x(d);
10088 background.attr("visibility", "hidden");
10089
10090 }
10091
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) + ")"; });
10098 }
10099
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);
10109
10110 }
10111
10112 function position(d) {
10113 var v = dragging[d];
10114 return v == null ? x(d) : v;
10115 }
10116 });
10117
10118 return chart;
10119 }
10120
10121 //============================================================
10122 // Expose Public Variables
10123 //------------------------------------------------------------
10124
10125 chart.dispatch = dispatch;
10126 chart.options = nv.utils.optionsFunc.bind(chart);
10127
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 = _;}},
10135
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 }},
10142
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 });
10154
10155 nv.utils.initOptions(chart);
10156 return chart;
10157 };
10158 nv.models.pie = function() {
10159 "use strict";
10160
10161 //============================================================
10162 // Public Variables with Default Settings
10163 //------------------------------------------------------------
10164
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')
10190 ;
10191
10192 var arcs = [];
10193 var arcsOver = [];
10194
10195 //============================================================
10196 // chart function
10197 //------------------------------------------------------------
10198
10199 var renderWatch = nv.utils.renderWatch(dispatch);
10200
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 = []
10209 ;
10210
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);
10218 }
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); }));
10223 }
10224 nv.utils.initSVG(container);
10225
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');
10233
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 + ')');
10237
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 });
10247
10248 arcs = [];
10249 arcsOver = [];
10250 for (var i = 0; i < data[0].length; i++) {
10251
10252 var arc = d3.svg.arc().outerRadius(arcsRadiusOuter[i]);
10253 var arcOver = d3.svg.arc().outerRadius(arcsRadiusOuter[i] + 5);
10254
10255 if (startAngle !== false) {
10256 arc.startAngle(startAngle);
10257 arcOver.startAngle(startAngle);
10258 }
10259 if (endAngle !== false) {
10260 arc.endAngle(endAngle);
10261 arcOver.endAngle(endAngle);
10262 }
10263 if (donut) {
10264 arc.innerRadius(arcsRadiusInner[i]);
10265 arcOver.innerRadius(arcsRadiusInner[i]);
10266 }
10267
10268 if (arc.cornerRadius && cornerRadius) {
10269 arc.cornerRadius(cornerRadius);
10270 arcOver.cornerRadius(cornerRadius);
10271 }
10272
10273 arcs.push(arc);
10274 arcsOver.push(arcOver);
10275 }
10276
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) });
10281
10282 // padAngle added in d3 3.5
10283 if (pie.padAngle && padAngle) {
10284 pie.padAngle(padAngle);
10285 }
10286
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');
10290
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 });
10301 }
10302
10303 var slices = wrap.select('.nv-pie').selectAll('.nv-slice').data(pie);
10304 var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label').data(pie);
10305
10306 slices.exit().remove();
10307 pieLabels.exit().remove();
10308
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]);
10317 }
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]);
10330 }
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 });
10350
10351 slices.attr('fill', function(d,i) { return color(d.data, i); });
10352 slices.attr('stroke', function(d,i) { return color(d.data, i); });
10353
10354 var paths = ae.append('path').each(function(d) {
10355 this._current = d;
10356 });
10357
10358 slices.select('path')
10359 .transition()
10360 .attr('d', function (d, i) { return arcs[i](d); })
10361 .attrTween('d', arcTween);
10362
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]);
10368
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);
10374 }
10375 } else if (!donut) {
10376 labelsArc[i].innerRadius(0);
10377 }
10378 }
10379
10380 pieLabels.enter().append("g").classed("nv-label",true).each(function(d,i) {
10381 var group = d3.select(this);
10382
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;
10392 }
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) + ')'
10398 }
10399 });
10400
10401 group.append('rect')
10402 .style('stroke', '#fff')
10403 .style('fill', '#fff')
10404 .attr("rx", 3)
10405 .attr("ry", 3);
10406
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 });
10411
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 };
10418
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;
10428 }
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
10433
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;
10444 }
10445 labelLocationHash[createHashKey(center)] = true;
10446 }
10447 return 'translate(' + center + ')'
10448 }
10449 });
10450
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 '';
10460
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;
10478 }
10479 }
10480 return label;
10481 })
10482 ;
10483 }
10484
10485
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;
10490 }
10491
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 };
10501 }
10502 });
10503
10504 renderWatch.renderEnd('pie immediate');
10505 return chart;
10506 }
10507
10508 //============================================================
10509 // Expose Public Variables
10510 //------------------------------------------------------------
10511
10512 chart.dispatch = dispatch;
10513 chart.options = nv.utils.optionsFunc.bind(chart);
10514
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=_;}},
10536
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 }},
10552
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 });
10570
10571 nv.utils.initOptions(chart);
10572 return chart;
10573 };
10574 nv.models.pieChart = function() {
10575 "use strict";
10576
10577 //============================================================
10578 // Public Variables with Default Settings
10579 //------------------------------------------------------------
10580
10581 var pie = nv.models.pie();
10582 var legend = nv.models.legend();
10583 var tooltip = nv.models.tooltip();
10584
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')
10596 ;
10597
10598 tooltip
10599 .headerEnabled(false)
10600 .duration(0)
10601 .valueFormatter(function(d, i) {
10602 return pie.valueFormat()(d, i);
10603 });
10604
10605 //============================================================
10606 // Private Variables
10607 //------------------------------------------------------------
10608
10609 var renderWatch = nv.utils.renderWatch(dispatch);
10610
10611 var stateGetter = function(data) {
10612 return function(){
10613 return {
10614 active: data.map(function(d) { return !d.disabled })
10615 };
10616 }
10617 };
10618
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 });
10625 }
10626 }
10627 };
10628
10629 //============================================================
10630 // Chart function
10631 //------------------------------------------------------------
10632
10633 function chart(selection) {
10634 renderWatch.reset();
10635 renderWatch.models(pie);
10636
10637 selection.each(function(data) {
10638 var container = d3.select(this);
10639 nv.utils.initSVG(container);
10640
10641 var that = this;
10642 var availableWidth = nv.utils.availableWidth(width, container, margin),
10643 availableHeight = nv.utils.availableHeight(height, container, margin);
10644
10645 chart.update = function() { container.transition().call(chart); };
10646 chart.container = this;
10647
10648 state.setter(stateSetter(data), chart.update)
10649 .getter(stateGetter(data))
10650 .update();
10651
10652 //set state.disabled
10653 state.disabled = data.map(function(d) { return !!d.disabled });
10654
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];
10663 }
10664 }
10665
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();
10672 }
10673
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');
10678
10679 gEnter.append('g').attr('class', 'nv-pieWrap');
10680 gEnter.append('g').attr('class', 'nv-legendWrap');
10681
10682 // Legend
10683 if (showLegend) {
10684 if (legendPosition === "top") {
10685 legend.width( availableWidth ).key(pie.x());
10686
10687 wrap.select('.nv-legendWrap')
10688 .datum(data)
10689 .call(legend);
10690
10691 if ( margin.top != legend.height()) {
10692 margin.top = legend.height();
10693 availableHeight = nv.utils.availableHeight(height, container, margin);
10694 }
10695
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)
10702 }
10703 legend.height(availableHeight).key(pie.x());
10704 legend.width(legendWidth);
10705 availableWidth -= legend.width();
10706
10707 wrap.select('.nv-legendWrap')
10708 .datum(data)
10709 .call(legend)
10710 .attr('transform', 'translate(' + (availableWidth) +',0)');
10711 }
10712 }
10713 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10714
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);
10719
10720 //============================================================
10721 // Event Handling/Dispatching (in chart's scope)
10722 //------------------------------------------------------------
10723
10724 legend.dispatch.on('stateChange', function(newState) {
10725 for (var key in newState) {
10726 state[key] = newState[key];
10727 }
10728 dispatch.stateChange(state);
10729 chart.update();
10730 });
10731
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;
10739 }
10740 chart.update();
10741 });
10742 });
10743
10744 renderWatch.renderEnd('pieChart immediate');
10745 return chart;
10746 }
10747
10748 //============================================================
10749 // Event Handling/Dispatching (out of chart's scope)
10750 //------------------------------------------------------------
10751
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 });
10760
10761 pie.dispatch.on('elementMouseout.tooltip', function(evt) {
10762 tooltip.hidden(true);
10763 });
10764
10765 pie.dispatch.on('elementMousemove.tooltip', function(evt) {
10766 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
10767 });
10768
10769 //============================================================
10770 // Expose Public Variables
10771 //------------------------------------------------------------
10772
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);
10779
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=_;}},
10787
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 }},
10799
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 };
10821
10822 nv.models.scatter = function() {
10823 "use strict";
10824
10825 //============================================================
10826 // Public Variables with Default Settings
10827 //------------------------------------------------------------
10828
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
10863 ;
10864
10865
10866 //============================================================
10867 // Private Variables
10868 //------------------------------------------------------------
10869
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]
10875 ;
10876
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);
10883
10884 nv.utils.initSVG(container);
10885
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 });
10892
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 );
10903
10904 x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
10905
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]);
10911
10912 y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
10913 .range(yRange || [availableHeight, 0]);
10914
10915 z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10916 .range(sizeRange || _sizeRange_def);
10917
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];
10920
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]);
10925
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]);
10930
10931 if ( isNaN(x.domain()[0])) {
10932 x.domain([-1,1]);
10933 }
10934
10935 if ( isNaN(y.domain()[0])) {
10936 y.domain([-1,1]);
10937 }
10938
10939 x0 = x0 || x;
10940 y0 = y0 || y;
10941 z0 = z0 || z;
10942
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');
10949
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');
10954
10955 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10956
10957 defsEnter.append('clipPath')
10958 .attr('id', 'nv-edge-clip-' + id)
10959 .append('rect');
10960
10961 wrap.select('#nv-edge-clip-' + id + ' rect')
10962 .attr('width', availableWidth)
10963 .attr('height', (availableHeight > 0) ? availableHeight : 0);
10964
10965 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10966
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;
10971
10972 if (!interactive) return false;
10973
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);
10985
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 );
10996
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]);
11004 }
11005
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 ]);
11014
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]
11020 }
11021 });
11022
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+")"; })
11037 ;
11038
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));
11045 }
11046
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);
11059 }
11060
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);
11067
11068 // standardize attributes for tooltip.
11069 point['x'] = getX(point);
11070 point['y'] = getY(point);
11071
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;
11076
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 };
11081
11082 mDispatch({
11083 point: point,
11084 series: series,
11085 pos: pos,
11086 seriesIndex: d.series,
11087 pointIndex: d.point
11088 });
11089 };
11090
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 });
11104
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];
11116
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];
11129
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];
11142
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];
11156
11157 dispatch.elementMouseout({
11158 point: point,
11159 series: series,
11160 seriesIndex: d.series,
11161 pointIndex: i,
11162 color: color(d, i)
11163 });
11164 });
11165 }
11166 }
11167
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);
11184
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)
11220 ;
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 );
11233
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();
11238
11239 //store old scales for use in transitions on update
11240 x0 = x.copy();
11241 y0 = y.copy();
11242 z0 = z.copy();
11243
11244 });
11245 renderWatch.renderEnd('scatter immediate');
11246 return chart;
11247 }
11248
11249 //============================================================
11250 // Expose Public Variables
11251 //------------------------------------------------------------
11252
11253 chart.dispatch = dispatch;
11254 chart.options = nv.utils.optionsFunc.bind(chart);
11255
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 };
11271
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 });
11276
11277 dispatch.on('elementMouseout.point', function(d) {
11278 if (interactive) chart._calls.highlightPoint(d.seriesIndex,d.pointIndex,false);
11279 });
11280
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=_;}},
11306
11307
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(_);}},
11313
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;
11332 }
11333 }}
11334 });
11335
11336 nv.utils.initOptions(chart);
11337 return chart;
11338 };
11339
11340 nv.models.scatterChart = function() {
11341 "use strict";
11342
11343 //============================================================
11344 // Public Variables with Default Settings
11345 //------------------------------------------------------------
11346
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()
11354 ;
11355
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
11374 ;
11375
11376 scatter.xScale(x).yScale(y);
11377 xAxis.orient('bottom').tickPadding(10);
11378 yAxis
11379 .orient((rightAlignYAxis) ? 'right' : 'left')
11380 .tickPadding(10)
11381 ;
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 });
11391
11392 //============================================================
11393 // Private Variables
11394 //------------------------------------------------------------
11395
11396 var x0, y0
11397 , renderWatch = nv.utils.renderWatch(dispatch, duration);
11398
11399 var stateGetter = function(data) {
11400 return function(){
11401 return {
11402 active: data.map(function(d) { return !d.disabled })
11403 };
11404 }
11405 };
11406
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 });
11413 }
11414 };
11415
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);
11423
11424 selection.each(function(data) {
11425 var that = this;
11426
11427 container = d3.select(this);
11428 nv.utils.initSVG(container);
11429
11430 var availableWidth = nv.utils.availableWidth(width, container, margin),
11431 availableHeight = nv.utils.availableHeight(height, container, margin);
11432
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;
11440
11441 state
11442 .setter(stateSetter(data), chart.update)
11443 .getter(stateGetter(data))
11444 .update();
11445
11446 // DEPRECATED set state.disableddisabled
11447 state.disabled = data.map(function(d) { return !!d.disabled });
11448
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];
11457 }
11458 }
11459
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();
11467 }
11468
11469 // Setup Scales
11470 x = scatter.xScale();
11471 y = scatter.yScale();
11472
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');
11478
11479 // background for pointer events
11480 gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
11481
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');
11488
11489 if (rightAlignYAxis) {
11490 g.select(".nv-y.nv-axis")
11491 .attr("transform", "translate(" + availableWidth + ",0)");
11492 }
11493
11494 // Legend
11495 if (showLegend) {
11496 var legendWidth = availableWidth;
11497 legend.width(legendWidth);
11498
11499 wrap.select('.nv-legendWrap')
11500 .datum(data)
11501 .call(legend);
11502
11503 if ( margin.top != legend.height()) {
11504 margin.top = legend.height();
11505 availableHeight = nv.utils.availableHeight(height, container, margin);
11506 }
11507
11508 wrap.select('.nv-legendWrap')
11509 .attr('transform', 'translate(0' + ',' + (-margin.top) +')');
11510 }
11511
11512 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11513
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 }));
11522
11523 wrap.select('.nv-scatterWrap')
11524 .datum(data.filter(function(d) { return !d.disabled }))
11525 .call(scatter);
11526
11527
11528 wrap.select('.nv-regressionLinesWrap')
11529 .attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
11530
11531 var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
11532 .data(function (d) {
11533 return d;
11534 });
11535
11536 regWrap.enter().append('g').attr('class', 'nv-regLines');
11537
11538 var regLine = regWrap.selectAll('.nv-regLine')
11539 .data(function (d) {
11540 return [d]
11541 });
11542
11543 regLine.enter()
11544 .append('line').attr('class', 'nv-regLine')
11545 .style('stroke-opacity', 0);
11546
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 });
11566
11567 // Setup Axes
11568 if (showXAxis) {
11569 xAxis
11570 .scale(x)
11571 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
11572 .tickSize( -availableHeight , 0);
11573
11574 g.select('.nv-x.nv-axis')
11575 .attr('transform', 'translate(0,' + y.range()[0] + ')')
11576 .call(xAxis);
11577 }
11578
11579 if (showYAxis) {
11580 yAxis
11581 .scale(y)
11582 ._ticks( nv.utils.calcTicksY(availableHeight/36, data) )
11583 .tickSize( -availableWidth, 0);
11584
11585 g.select('.nv-y.nv-axis')
11586 .call(yAxis);
11587 }
11588
11589
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);
11604 }
11605
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);
11620 }
11621
11622 //============================================================
11623 // Event Handling/Dispatching (in chart's scope)
11624 //------------------------------------------------------------
11625
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 });
11632
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;
11640 }
11641 chart.update();
11642 });
11643
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 });
11652
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 });
11660
11661 //store old scales for use in transitions on update
11662 x0 = x.copy();
11663 y0 = y.copy();
11664
11665 });
11666
11667 renderWatch.renderEnd('scatter with line immediate');
11668 return chart;
11669 }
11670
11671 //============================================================
11672 // Expose Public Variables
11673 //------------------------------------------------------------
11674
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;
11684
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=_;}},
11699
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 }},
11719
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 });
11738
11739 nv.utils.inheritOptions(chart, scatter);
11740 nv.utils.initOptions(chart);
11741 return chart;
11742 };
11743
11744 nv.models.sparkline = function() {
11745 "use strict";
11746
11747 //============================================================
11748 // Public Variables with Default Settings
11749 //------------------------------------------------------------
11750
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
11765 ;
11766
11767 function chart(selection) {
11768 selection.each(function(data) {
11769 var availableWidth = width - margin.left - margin.right,
11770 availableHeight = height - margin.top - margin.bottom;
11771
11772 container = d3.select(this);
11773 nv.utils.initSVG(container);
11774
11775 // Setup Scales
11776 x .domain(xDomain || d3.extent(data, getX ))
11777 .range(xRange || [0, availableWidth]);
11778
11779 y .domain(yDomain || d3.extent(data, getY ))
11780 .range(yRange || [availableHeight, 0]);
11781
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');
11787
11788 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
11789
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 );
11800
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;
11812 }
11813 }
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 });
11830
11831 return chart;
11832 }
11833
11834 //============================================================
11835 // Expose Public Variables
11836 //------------------------------------------------------------
11837
11838 chart.options = nv.utils.optionsFunc.bind(chart);
11839
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=_;}},
11851
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(_);}},
11855
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 });
11867
11868 nv.utils.initOptions(chart);
11869 return chart;
11870 };
11871
11872 nv.models.sparklinePlus = function() {
11873 "use strict";
11874
11875 //============================================================
11876 // Public Variables with Default Settings
11877 //------------------------------------------------------------
11878
11879 var sparkline = nv.models.sparkline();
11880
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
11894 ;
11895
11896 function chart(selection) {
11897 selection.each(function(data) {
11898 var container = d3.select(this);
11899 nv.utils.initSVG(container);
11900
11901 var availableWidth = nv.utils.availableWidth(width, container, margin),
11902 availableHeight = nv.utils.availableHeight(height, container, margin);
11903
11904 chart.update = function() { container.call(chart); };
11905 chart.container = this;
11906
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();
11913 }
11914
11915 var currentValue = sparkline.y()(data[data.length-1], data.length-1);
11916
11917 // Setup Scales
11918 x = sparkline.xScale();
11919 y = sparkline.yScale();
11920
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');
11926
11927 gEnter.append('g').attr('class', 'nv-sparklineWrap');
11928 gEnter.append('g').attr('class', 'nv-valueWrap');
11929 gEnter.append('g').attr('class', 'nv-hoverArea');
11930
11931 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11932
11933 // Main Chart Component(s)
11934 var sparklineWrap = g.select('.nv-sparklineWrap');
11935
11936 sparkline.width(availableWidth).height(availableHeight);
11937 sparklineWrap.call(sparkline);
11938
11939 if (showLastValue) {
11940 var valueWrap = g.select('.nv-valueWrap');
11941 var value = valueWrap.selectAll('.nv-currentValue')
11942 .data([currentValue]);
11943
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');
11948
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));
11956 }
11957
11958 gEnter.select('.nv-hoverArea').append('rect')
11959 .on('mousemove', sparklineHover)
11960 .on('click', function() { paused = !paused })
11961 .on('mouseout', function() { index = []; updateValueLine(); });
11962
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);
11967
11968 //index is currently global (within the chart), may or may not keep it that way
11969 function updateValueLine() {
11970 if (paused) return;
11971
11972 var hoverValue = g.selectAll('.nv-hoverValue').data(index);
11973
11974 var hoverEnter = hoverValue.enter()
11975 .append('g').attr('class', 'nv-hoverValue')
11976 .style('stroke-opacity', 0)
11977 .style('fill-opacity', 0);
11978
11979 hoverValue.exit()
11980 .transition().duration(250)
11981 .style('stroke-opacity', 0)
11982 .style('fill-opacity', 0)
11983 .remove();
11984
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);
11990
11991 if (!index.length) return;
11992
11993 hoverEnter.append('line')
11994 .attr('x1', 0)
11995 .attr('y1', -margin.top)
11996 .attr('x2', 0)
11997 .attr('y2', availableHeight);
11998
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');
12004
12005 g.select('.nv-hoverValue .nv-xValue')
12006 .text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
12007
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');
12013
12014 g.select('.nv-hoverValue .nv-yValue')
12015 .text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
12016 }
12017
12018 function sparklineHover() {
12019 if (paused) return;
12020
12021 var pos = d3.mouse(this)[0] - margin.left;
12022
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;
12030 }
12031 }
12032 return closestIndex;
12033 }
12034
12035 index = [getClosestIndex(data, Math.round(x.invert(pos)))];
12036 updateValueLine();
12037 }
12038
12039 });
12040
12041 return chart;
12042 }
12043
12044 //============================================================
12045 // Expose Public Variables
12046 //------------------------------------------------------------
12047
12048 // expose chart's sub-components
12049 chart.sparkline = sparkline;
12050
12051 chart.options = nv.utils.optionsFunc.bind(chart);
12052
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=_;}},
12063
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 });
12072
12073 nv.utils.inheritOptions(chart, sparkline);
12074 nv.utils.initOptions(chart);
12075
12076 return chart;
12077 };
12078
12079 nv.models.stackedArea = function() {
12080 "use strict";
12081
12082 //============================================================
12083 // Public Variables with Default Settings
12084 //------------------------------------------------------------
12085
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')
12104 ;
12105
12106 scatter
12107 .pointSize(2.2) // default size
12108 .pointDomain([2.2, 2.2]) // all the same size by default
12109 ;
12110
12111 /************************************
12112 * offset:
12113 * 'wiggle' (stream)
12114 * 'zero' (stacked)
12115 * 'expand' (normalize to 100%)
12116 * 'silhouette' (simple centered)
12117 *
12118 * order:
12119 * 'inside-out' (stream)
12120 * 'default' (input order)
12121 ************************************/
12122
12123 var renderWatch = nv.utils.renderWatch(dispatch, duration);
12124
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;
12131
12132 container = d3.select(this);
12133 nv.utils.initSVG(container);
12134
12135 // Setup Scales
12136 x = scatter.xScale();
12137 y = scatter.yScale();
12138
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 });
12149
12150 var dataFiltered = data.filter(function(series) {
12151 return !series.disabled;
12152 });
12153
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);
12167
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');
12174
12175 gEnter.append('g').attr('class', 'nv-areaWrap');
12176 gEnter.append('g').attr('class', 'nv-scatterWrap');
12177
12178 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12179
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);
12184 }
12185
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 }));
12195
12196 var scatterWrap = g.select('.nv-scatterWrap')
12197 .datum(data);
12198
12199 scatterWrap.call(scatter);
12200
12201 defsEnter.append('clipPath')
12202 .attr('id', 'nv-edge-clip-' + id)
12203 .append('rect');
12204
12205 wrap.select('#nv-edge-clip-' + id + ' rect')
12206 .attr('width', availableWidth)
12207 .attr('height', availableHeight);
12208
12209 g.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
12210
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);
12220
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) });
12225
12226 var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
12227 .data(function(d) { return d });
12228
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 });
12260
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 });
12270
12271 //============================================================
12272 // Event Handling/Dispatching (in chart's scope)
12273 //------------------------------------------------------------
12274
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 });
12281
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 = [];
12290
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.
12294 }
12295
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;
12301 }
12302 }
12303 }
12304 for (j = 0; j < m; ++j) y0[j] = 0;
12305 return y0;
12306 };
12307
12308 });
12309
12310 renderWatch.renderEnd('stackedArea immediate');
12311 return chart;
12312 }
12313
12314 //============================================================
12315 // Global getters and setters
12316 //------------------------------------------------------------
12317
12318 chart.dispatch = dispatch;
12319 chart.scatter = scatter;
12320
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); });
12324
12325 chart.interpolate = function(_) {
12326 if (!arguments.length) return interpolate;
12327 interpolate = _;
12328 return chart;
12329 };
12330
12331 chart.duration = function(_) {
12332 if (!arguments.length) return duration;
12333 duration = _;
12334 renderWatch.reset(duration);
12335 scatter.duration(duration);
12336 return chart;
12337 };
12338
12339 chart.dispatch = dispatch;
12340 chart.scatter = scatter;
12341 chart.options = nv.utils.optionsFunc.bind(chart);
12342
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=_;}},
12351
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(_);}},
12355
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;
12389 }
12390 }},
12391 duration: {get: function(){return duration;}, set: function(_){
12392 duration = _;
12393 renderWatch.reset(duration);
12394 scatter.duration(duration);
12395 }}
12396 });
12397
12398 nv.utils.inheritOptions(chart, scatter);
12399 nv.utils.initOptions(chart);
12400
12401 return chart;
12402 };
12403
12404 nv.models.stackedAreaChart = function() {
12405 "use strict";
12406
12407 //============================================================
12408 // Public Variables with Default Settings
12409 //------------------------------------------------------------
12410
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()
12418 ;
12419
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
12440 ;
12441
12442 state.style = stacked.style();
12443 xAxis.orient('bottom').tickPadding(7);
12444 yAxis.orient((rightAlignYAxis) ? 'right' : 'left');
12445
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 });
12453
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 });
12461
12462 var oldYTickFormat = null,
12463 oldValueFormatter = null;
12464
12465 controls.updateState(false);
12466
12467 //============================================================
12468 // Private Variables
12469 //------------------------------------------------------------
12470
12471 var renderWatch = nv.utils.renderWatch(dispatch);
12472 var style = stacked.style();
12473
12474 var stateGetter = function(data) {
12475 return function(){
12476 return {
12477 active: data.map(function(d) { return !d.disabled }),
12478 style: stacked.style()
12479 };
12480 }
12481 };
12482
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 });
12491 }
12492 };
12493
12494 var percentFormatter = d3.format('%');
12495
12496 function chart(selection) {
12497 renderWatch.reset();
12498 renderWatch.models(stacked);
12499 if (showXAxis) renderWatch.models(xAxis);
12500 if (showYAxis) renderWatch.models(yAxis);
12501
12502 selection.each(function(data) {
12503 var container = d3.select(this),
12504 that = this;
12505 nv.utils.initSVG(container);
12506
12507 var availableWidth = nv.utils.availableWidth(width, container, margin),
12508 availableHeight = nv.utils.availableHeight(height, container, margin);
12509
12510 chart.update = function() { container.transition().duration(duration).call(chart); };
12511 chart.container = this;
12512
12513 state
12514 .setter(stateSetter(data), chart.update)
12515 .getter(stateGetter(data))
12516 .update();
12517
12518 // DEPRECATED set state.disabled
12519 state.disabled = data.map(function(d) { return !!d.disabled });
12520
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];
12529 }
12530 }
12531
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();
12538 }
12539
12540 // Setup Scales
12541 x = stacked.xScale();
12542 y = stacked.yScale();
12543
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');
12548
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');
12556
12557 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
12558
12559 // Legend
12560 if (showLegend) {
12561 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
12562
12563 legend.width(legendWidth);
12564 g.select('.nv-legendWrap').datum(data).call(legend);
12565
12566 if ( margin.top != legend.height()) {
12567 margin.top = legend.height();
12568 availableHeight = nv.utils.availableHeight(height, container, margin);
12569 }
12570
12571 g.select('.nv-legendWrap')
12572 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
12573 }
12574
12575 // Controls
12576 if (showControls) {
12577 var controlsData = [
12578 {
12579 key: controlLabels.stacked || 'Stacked',
12580 metaKey: 'Stacked',
12581 disabled: stacked.style() != 'stack',
12582 style: 'stack'
12583 },
12584 {
12585 key: controlLabels.stream || 'Stream',
12586 metaKey: 'Stream',
12587 disabled: stacked.style() != 'stream',
12588 style: 'stream'
12589 },
12590 {
12591 key: controlLabels.expanded || 'Expanded',
12592 metaKey: 'Expanded',
12593 disabled: stacked.style() != 'expand',
12594 style: 'expand'
12595 },
12596 {
12597 key: controlLabels.stack_percent || 'Stack %',
12598 metaKey: 'Stack_Percent',
12599 disabled: stacked.style() != 'stack_percent',
12600 style: 'stack_percent'
12601 }
12602 ];
12603
12604 controlWidth = (controlOptions.length/3) * 260;
12605 controlsData = controlsData.filter(function(d) {
12606 return controlOptions.indexOf(d.metaKey) !== -1;
12607 });
12608
12609 controls
12610 .width( controlWidth )
12611 .color(['#444', '#444', '#444']);
12612
12613 g.select('.nv-controlsWrap')
12614 .datum(controlsData)
12615 .call(controls);
12616
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);
12620 }
12621
12622 g.select('.nv-controlsWrap')
12623 .attr('transform', 'translate(0,' + (-margin.top) +')');
12624 }
12625
12626 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12627
12628 if (rightAlignYAxis) {
12629 g.select(".nv-y.nv-axis")
12630 .attr("transform", "translate(" + availableWidth + ",0)");
12631 }
12632
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);
12642 }
12643
12644 stacked
12645 .width(availableWidth)
12646 .height(availableHeight);
12647
12648 var stackedWrap = g.select('.nv-stackedWrap')
12649 .datum(data);
12650
12651 stackedWrap.transition().call(stacked);
12652
12653 // Setup Axes
12654 if (showXAxis) {
12655 xAxis.scale(x)
12656 ._ticks( nv.utils.calcTicksX(availableWidth/100, data) )
12657 .tickSize( -availableHeight, 0);
12658
12659 g.select('.nv-x.nv-axis')
12660 .attr('transform', 'translate(0,' + availableHeight + ')');
12661
12662 g.select('.nv-x.nv-axis')
12663 .transition().duration(0)
12664 .call(xAxis);
12665 }
12666
12667 if (showYAxis) {
12668 var ticks;
12669 if (stacked.offset() === 'wiggle') {
12670 ticks = 0;
12671 }
12672 else {
12673 ticks = nv.utils.calcTicksY(availableHeight/36, data);
12674 }
12675 yAxis.scale(y)
12676 ._ticks(ticks)
12677 .tickSize(-availableWidth, 0);
12678
12679 if (stacked.style() === 'expand' || stacked.style() === 'stack_percent') {
12680 var currentFormat = yAxis.tickFormat();
12681
12682 if ( !oldYTickFormat || currentFormat !== percentFormatter )
12683 oldYTickFormat = currentFormat;
12684
12685 //Forces the yAxis to use percentage in 'expand' mode.
12686 yAxis.tickFormat(percentFormatter);
12687 }
12688 else {
12689 if (oldYTickFormat) {
12690 yAxis.tickFormat(oldYTickFormat);
12691 oldYTickFormat = null;
12692 }
12693 }
12694
12695 g.select('.nv-y.nv-axis')
12696 .transition().duration(0)
12697 .call(yAxis);
12698 }
12699
12700 //============================================================
12701 // Event Handling/Dispatching (in chart's scope)
12702 //------------------------------------------------------------
12703
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 });
12713
12714 state.disabled = data.map(function(d) { return !!d.disabled });
12715 dispatch.stateChange(state);
12716
12717 chart.update();
12718 });
12719
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 });
12726
12727 controls.dispatch.on('legendClick', function(d,i) {
12728 if (!d.disabled) return;
12729
12730 controlsData = controlsData.map(function(s) {
12731 s.disabled = true;
12732 return s;
12733 });
12734 d.disabled = false;
12735
12736 stacked.style(d.style);
12737
12738
12739 state.style = stacked.style();
12740 dispatch.stateChange(state);
12741
12742 chart.update();
12743 });
12744
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);
12759 }
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));
12763
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 });
12773
12774 allData.reverse();
12775
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) {
12781
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))
12788 {
12789 indexToHighlight = i;
12790 return;
12791 }
12792 });
12793 if (indexToHighlight != null)
12794 allData[indexToHighlight].highlight = true;
12795 }
12796
12797 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
12798
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;
12804 }
12805 //Forces the tooltip to use percentage in 'expand' mode.
12806 valueFormatter = d3.format(".1%");
12807 }
12808 else {
12809 if (oldValueFormatter) {
12810 valueFormatter = oldValueFormatter;
12811 oldValueFormatter = null;
12812 }
12813 }
12814
12815 interactiveLayer.tooltip
12816 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
12817 .chartContainer(that.parentNode)
12818 .valueFormatter(valueFormatter)
12819 .data(
12820 {
12821 value: xValue,
12822 series: allData
12823 }
12824 )();
12825
12826 interactiveLayer.renderGuideLine(pointXLocation);
12827
12828 });
12829
12830 interactiveLayer.dispatch.on("elementMouseout",function(e) {
12831 stacked.clearHighlights();
12832 });
12833
12834 // Update chart from a state object passed to event handler
12835 dispatch.on('changeState', function(e) {
12836
12837 if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
12838 data.forEach(function(series,i) {
12839 series.disabled = e.disabled[i];
12840 });
12841
12842 state.disabled = e.disabled;
12843 }
12844
12845 if (typeof e.style !== 'undefined') {
12846 stacked.style(e.style);
12847 style = e.style;
12848 }
12849
12850 chart.update();
12851 });
12852
12853 });
12854
12855 renderWatch.renderEnd('stacked Area chart immediate');
12856 return chart;
12857 }
12858
12859 //============================================================
12860 // Event Handling/Dispatching (out of chart's scope)
12861 //------------------------------------------------------------
12862
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 });
12868
12869 stacked.dispatch.on('elementMouseout.tooltip', function(evt) {
12870 tooltip.hidden(true)
12871 });
12872
12873 //============================================================
12874 // Expose Public Variables
12875 //------------------------------------------------------------
12876
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;
12886
12887 chart.dispatch = dispatch;
12888 chart.options = nv.utils.optionsFunc.bind(chart);
12889
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=_;}},
12902
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 }},
12914
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 });
12945
12946 nv.utils.inheritOptions(chart, stacked);
12947 nv.utils.initOptions(chart);
12948
12949 return chart;
12950 };
12951 // based on http://bl.ocks.org/kerryrodden/477c1bfb081b783f80ad
12952 nv.models.sunburst = function() {
12953 "use strict";
12954
12955 //============================================================
12956 // Public Variables with Default Settings
12957 //------------------------------------------------------------
12958
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')
12969 ;
12970
12971 var x = d3.scale.linear().range([0, 2 * Math.PI]);
12972 var y = d3.scale.sqrt();
12973
12974 var partition = d3.layout.partition()
12975 .sort(null)
12976 .value(function(d) { return 1; });
12977
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)); });
12983
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;
12988
12989 //============================================================
12990 // chart function
12991 //------------------------------------------------------------
12992
12993 var renderWatch = nv.utils.renderWatch(dispatch);
12994
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;
13003
13004 nv.utils.initSVG(container);
13005
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);
13009
13010 var g = wrapEnter.selectAll('nv-sunburst');
13011
13012 wrap.attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
13013
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 });
13022
13023 y.range([0, radius]);
13024
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));
13048 }
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 });
13069
13070
13071
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;
13076 }
13077
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);
13081
13082 function tween(t) {
13083 var b = oi(t);
13084 a.x0 = b.x;
13085 a.dx0 = b.dx;
13086 return arc(b);
13087 }
13088
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;
13099 }
13100 }
13101
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);
13111 }
13112 : function (t) {
13113 x.domain(xd(t));
13114 y.domain(yd(t)).range(yr(t));
13115 return arc(d);
13116 };
13117 };
13118 }
13119
13120 });
13121
13122 renderWatch.renderEnd('sunburst immediate');
13123 return chart;
13124 }
13125
13126 //============================================================
13127 // Expose Public Variables
13128 //------------------------------------------------------------
13129
13130 chart.dispatch = dispatch;
13131 chart.options = nv.utils.optionsFunc.bind(chart);
13132
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=_;}},
13140
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 });
13152
13153 nv.utils.initOptions(chart);
13154 return chart;
13155 };
13156 nv.models.sunburstChart = function() {
13157 "use strict";
13158
13159 //============================================================
13160 // Public Variables with Default Settings
13161 //------------------------------------------------------------
13162
13163 var sunburst = nv.models.sunburst();
13164 var tooltip = nv.models.tooltip();
13165
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')
13175 ;
13176
13177 //============================================================
13178 // Private Variables
13179 //------------------------------------------------------------
13180
13181 var renderWatch = nv.utils.renderWatch(dispatch);
13182 tooltip.headerEnabled(false).duration(0).valueFormatter(function(d, i) {
13183 return d;
13184 });
13185
13186 //============================================================
13187 // Chart function
13188 //------------------------------------------------------------
13189
13190 function chart(selection) {
13191 renderWatch.reset();
13192 renderWatch.models(sunburst);
13193
13194 selection.each(function(data) {
13195 var container = d3.select(this);
13196 nv.utils.initSVG(container);
13197
13198 var that = this;
13199 var availableWidth = nv.utils.availableWidth(width, container, margin),
13200 availableHeight = nv.utils.availableHeight(height, container, margin);
13201
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;
13209
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();
13216 }
13217
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');
13222
13223 gEnter.append('g').attr('class', 'nv-sunburstWrap');
13224
13225 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13226
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);
13231
13232 });
13233
13234 renderWatch.renderEnd('sunburstChart immediate');
13235 return chart;
13236 }
13237
13238 //============================================================
13239 // Event Handling/Dispatching (out of chart's scope)
13240 //------------------------------------------------------------
13241
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 });
13250
13251 sunburst.dispatch.on('elementMouseout.tooltip', function(evt) {
13252 tooltip.hidden(true);
13253 });
13254
13255 sunburst.dispatch.on('elementMousemove.tooltip', function(evt) {
13256 tooltip.position({top: d3.event.pageY, left: d3.event.pageX})();
13257 });
13258
13259 //============================================================
13260 // Expose Public Variables
13261 //------------------------------------------------------------
13262
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);
13268
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=_;}},
13274
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 };
13296
13297 nv.version = "1.8.1";
13298 })();