3
var nv = window.nv || {};
6
nv.version = '1.1.15b';
7
nv.dev = true //set false when in production
11
nv.tooltip = nv.tooltip || {}; // For the tooltip system
12
nv.utils = nv.utils || {}; // Utility subsystem
13
nv.models = nv.models || {}; //stores all the possible models/components
14
nv.charts = {}; //stores all the ready to use charts
15
nv.graphs = []; //stores all the graphs currently on the page
16
nv.logs = {}; //stores some statistics and potential error messages
18
nv.dispatch = d3.dispatch('render_start', 'render_end');
20
// *************************************************************************
21
// Development render timers - disabled if dev = false
24
nv.dispatch.on('render_start', function(e) {
25
nv.logs.startTime = +new Date();
28
nv.dispatch.on('render_end', function(e) {
29
nv.logs.endTime = +new Date();
30
nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
31
nv.log('total', nv.logs.totalTime); // used for development, to keep track of graph generation times
35
// ********************************************
36
// Public Core NV functions
38
// Logs all arguments, and returns the last so you can test things in place
39
// Note: in IE8 console.log is an object not a function, and if modernizr is used
40
// then calling Function.prototype.bind with with anything other than a function
41
// causes a TypeError to be thrown.
43
if (nv.dev && console.log && console.log.apply)
44
console.log.apply(console, arguments)
45
else if (nv.dev && typeof console.log == "function" && Function.prototype.bind) {
46
var log = Function.prototype.bind.call(console.log, console);
47
log.apply(console, arguments);
49
return arguments[arguments.length - 1];
53
nv.render = function render(step) {
54
step = step || 1; // number of graphs to generate in each timeout loop
56
nv.render.active = true;
57
nv.dispatch.render_start();
59
setTimeout(function() {
62
for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
63
chart = graph.generate();
64
if (typeof graph.callback == typeof(Function)) graph.callback(chart);
65
nv.graphs.push(chart);
68
nv.render.queue.splice(0, i);
70
if (nv.render.queue.length) setTimeout(arguments.callee, 0);
72
nv.dispatch.render_end();
73
nv.render.active = false;
78
nv.render.active = false;
81
nv.addGraph = function(obj) {
82
if (typeof arguments[0] === typeof(Function))
83
obj = {generate: arguments[0], callback: arguments[1]};
85
nv.render.queue.push(obj);
87
if (!nv.render.active) nv.render();
90
nv.identity = function(d) { return d; };
92
nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
94
function daysInMonth(month,year) {
95
return (new Date(year, month+1, 0)).getDate();
98
function d3_time_range(floor, step, number) {
99
return function(t0, t1, dt) {
100
var time = floor(t0), times = [];
101
if (time < t0) step(time);
104
var date = new Date(+time);
105
if ((number(date) % dt === 0)) times.push(date);
109
while (time < t1) { times.push(new Date(+time)); step(time); }
115
d3.time.monthEnd = function(date) {
116
return new Date(date.getFullYear(), date.getMonth(), 0);
119
d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
120
date.setUTCDate(date.getUTCDate() + 1);
121
date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
123
return date.getMonth();
127
/* Utility class to handle creation of an interactive layer.
128
This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
129
containing the X-coordinate. It can also render a vertical line where the mouse is located.
131
dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
132
the rectangle. The dispatch is given one object which contains the mouseX/Y location.
133
It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
135
nv.interactiveGuideline = function() {
137
var tooltip = nv.models.tooltip();
141
//Please pass in the bounding chart's top and left margins
142
//This is important for calculating the correct mouseX/Y positions.
143
, margin = {left: 0, top: 0}
144
, xScale = d3.scale.linear()
145
, yScale = d3.scale.linear()
146
, dispatch = d3.dispatch('elementMousemove', 'elementMouseout','elementDblclick')
147
, showGuideLine = true
148
, svgContainer = null
149
//Must pass in the bounding chart's <svg> container.
150
//The mousemove event is attached to this container.
154
var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1 //Check user-agent for Microsoft Internet Explorer.
158
function layer(selection) {
159
selection.each(function(data) {
160
var container = d3.select(this);
162
var availableWidth = (width || 960), availableHeight = (height || 400);
164
var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]);
165
var wrapEnter = wrap.enter()
166
.append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
169
wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
175
function mouseHandler() {
176
var d3mouse = d3.mouse(this);
177
var mouseX = d3mouse[0];
178
var mouseY = d3mouse[1];
179
var subtractMargin = true;
180
var mouseOutAnyReason = false;
183
D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
184
d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
185
over a rect in IE 10.
186
However, d3.event.offsetX/Y also returns the mouse coordinates
187
relative to the triggering <rect>. So we use offsetX/Y on IE.
189
mouseX = d3.event.offsetX;
190
mouseY = d3.event.offsetY;
193
On IE, if you attach a mouse event listener to the <svg> container,
194
it will actually trigger it for all the child elements (like <path>, <circle>, etc).
195
When this happens on IE, the offsetX/Y is set to where ever the child element
197
As a result, we do NOT need to subtract margins to figure out the mouse X/Y
198
position under this scenario. Removing the line below *will* cause
199
the interactive layer to not work right on IE.
201
if(d3.event.target.tagName !== "svg")
202
subtractMargin = false;
204
if (d3.event.target.className.baseVal.match("nv-legend"))
205
mouseOutAnyReason = true;
210
mouseX -= margin.left;
211
mouseY -= margin.top;
214
/* If mouseX/Y is outside of the chart's bounds,
215
trigger a mouseOut event.
217
if (mouseX < 0 || mouseY < 0
218
|| mouseX > availableWidth || mouseY > availableHeight
219
|| (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
224
if (d3.event.relatedTarget
225
&& d3.event.relatedTarget.ownerSVGElement === undefined
226
&& d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
230
dispatch.elementMouseout({
234
layer.renderGuideLine(null); //hide the guideline
238
var pointXValue = xScale.invert(mouseX);
239
dispatch.elementMousemove({
242
pointXValue: pointXValue
245
//If user double clicks the layer, fire a elementDblclick dispatch.
246
if (d3.event.type === "dblclick") {
247
dispatch.elementDblclick({
250
pointXValue: pointXValue
256
.on("mousemove",mouseHandler, true)
257
.on("mouseout" ,mouseHandler,true)
258
.on("dblclick" ,mouseHandler)
261
//Draws a vertical guideline at the given X postion.
262
layer.renderGuideLine = function(x) {
263
if (!showGuideLine) return;
264
var line = wrap.select(".nv-interactiveGuideLine")
266
.data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
270
.attr("class", "nv-guideline")
271
.attr("x1", function(d) { return d;})
272
.attr("x2", function(d) { return d;})
273
.attr("y1", availableHeight)
276
line.exit().remove();
282
layer.dispatch = dispatch;
283
layer.tooltip = tooltip;
285
layer.margin = function(_) {
286
if (!arguments.length) return margin;
287
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
288
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
292
layer.width = function(_) {
293
if (!arguments.length) return width;
298
layer.height = function(_) {
299
if (!arguments.length) return height;
304
layer.xScale = function(_) {
305
if (!arguments.length) return xScale;
310
layer.showGuideLine = function(_) {
311
if (!arguments.length) return showGuideLine;
316
layer.svgContainer = function(_) {
317
if (!arguments.length) return svgContainer;
326
/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
327
This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
329
For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
330
Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
331
because 28 is closer to 30 than 10.
333
Unit tests can be found in: interactiveBisectTest.html
335
Has the following known issues:
336
* Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
337
* Won't work if there are duplicate x coordinate values.
339
nv.interactiveBisect = function (values, searchVal, xAccessor) {
341
if (! values instanceof Array) return null;
342
if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;}
344
var bisect = d3.bisector(xAccessor).left;
345
var index = d3.max([0, bisect(values,searchVal) - 1]);
346
var currentValue = xAccessor(values[index], index);
347
if (typeof currentValue === 'undefined') currentValue = index;
349
if (currentValue === searchVal) return index; //found exact match
351
var nextIndex = d3.min([index+1, values.length - 1]);
352
var nextValue = xAccessor(values[nextIndex], nextIndex);
353
if (typeof nextValue === 'undefined') nextValue = nextIndex;
355
if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal))
362
Returns the index in the array "values" that is closest to searchVal.
363
Only returns an index if searchVal is within some "threshold".
364
Otherwise, returns null.
366
nv.nearestValueIndex = function (values, searchVal, threshold) {
368
var yDistMax = Infinity, indexToHighlight = null;
369
values.forEach(function(d,i) {
370
var delta = Math.abs(searchVal - d);
371
if ( delta <= yDistMax && delta < threshold) {
373
indexToHighlight = i;
376
return indexToHighlight;
377
};/* Tooltip rendering model for nvd3 charts.
378
window.nv.models.tooltip is the updated,new way to render tooltips.
380
window.nv.tooltip.show is the old tooltip code.
381
window.nv.tooltip.* also has various helper methods.
385
window.nv.tooltip = {};
387
/* Model which can be instantiated to handle tooltip rendering.
389
var tip = nv.models.tooltip().gravity('w').distance(23)
392
tip(); //just invoke the returned function to render tooltip.
394
window.nv.models.tooltip = function() {
395
var content = null //HTML contents of the tooltip. If null, the content is generated via the data variable.
396
, data = null /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
400
value: "August 2009",
417
, gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
418
, distance = 50 //Distance to offset tooltip from the mouse location.
419
, snapDistance = 25 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
420
, fixedTop = null //If not null, this fixes the top position of the tooltip.
421
, classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
422
, chartContainer = null //Parent DIV, of the SVG Container that holds the chart.
423
, tooltipElem = null //actual DOM element representing the tooltip.
424
, position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
425
, enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
426
//Generates a unique id when you create a new tooltip() object
427
, id = "nvtooltip-" + Math.floor(Math.random() * 100000)
430
//CSS class to specify whether element should not have mouse events.
431
var nvPointerEventsClass = "nv-pointer-events-none";
433
//Format function for the tooltip values column
434
var valueFormatter = function(d,i) {
438
//Format function for the tooltip header value.
439
var headerFormatter = function(d) {
443
//By default, the tooltip model renders a beautiful table inside a DIV.
444
//You can override this function if a custom tooltip is desired.
445
var contentGenerator = function(d) {
446
if (content != null) return content;
448
if (d == null) return '';
450
var table = d3.select(document.createElement("table"));
451
var theadEnter = table.selectAll("thead")
453
.enter().append("thead");
454
theadEnter.append("tr")
458
.classed("x-value",true)
459
.html(headerFormatter(d.value));
461
var tbodyEnter = table.selectAll("tbody")
463
.enter().append("tbody");
464
var trowEnter = tbodyEnter.selectAll("tr")
465
.data(function(p) { return p.series})
468
.classed("highlight", function(p) { return p.highlight})
471
trowEnter.append("td")
472
.classed("legend-color-guide",true)
474
.style("background-color", function(p) { return p.color});
475
trowEnter.append("td")
477
.html(function(p) {return p.key});
478
trowEnter.append("td")
479
.classed("value",true)
480
.html(function(p,i) { return valueFormatter(p.value,i) });
483
trowEnter.selectAll("td").each(function(p) {
485
var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
488
.style("border-bottom-color", opacityScale(opacity))
489
.style("border-top-color", opacityScale(opacity))
494
var html = table.node().outerHTML;
495
if (d.footer !== undefined)
496
html += "<div class='footer'>" + d.footer + "</div>";
501
var dataSeriesExists = function(d) {
502
if (d && d.series && d.series.length > 0) return true;
507
//In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
508
function convertViewBoxRatio() {
509
if (chartContainer) {
510
var svg = d3.select(chartContainer);
511
if (svg.node().tagName !== "svg") {
512
svg = svg.select("svg");
514
var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
516
viewBox = viewBox.split(' ');
517
var ratio = parseInt(svg.style('width')) / viewBox[2];
519
position.left = position.left * ratio;
520
position.top = position.top * ratio;
525
//Creates new tooltip container, or uses existing one on DOM.
526
function getTooltipContainer(newContent) {
529
body = d3.select(chartContainer);
531
body = d3.select("body");
533
var container = body.select(".nvtooltip");
534
if (container.node() === null) {
535
//Create new tooltip div if it doesn't exist on DOM.
536
container = body.append("div")
537
.attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
543
container.node().innerHTML = newContent;
544
container.style("top",0).style("left",0).style("opacity",0);
545
container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
546
container.classed(nvPointerEventsClass,true);
547
return container.node();
552
//Draw the tooltip onto the DOM.
553
function nvtooltip() {
554
if (!enabled) return;
555
if (!dataSeriesExists(data)) return;
557
convertViewBoxRatio();
559
var left = position.left;
560
var top = (fixedTop != null) ? fixedTop : position.top;
561
var container = getTooltipContainer(contentGenerator(data));
562
tooltipElem = container;
563
if (chartContainer) {
564
var svgComp = chartContainer.getElementsByTagName("svg")[0];
565
var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
566
var svgOffset = {left:0,top:0};
568
var svgBound = svgComp.getBoundingClientRect();
569
var chartBound = chartContainer.getBoundingClientRect();
570
var svgBoundTop = svgBound.top;
572
//Defensive code. Sometimes, svgBoundTop can be a really negative
573
// number, like -134254. That's a bug.
574
// If such a number is found, use zero instead. FireFox bug only
575
if (svgBoundTop < 0) {
576
var containerBound = chartContainer.getBoundingClientRect();
577
svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
579
svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
580
svgOffset.left = Math.abs(svgBound.left - chartBound.left);
582
//If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
583
//You need to also add any offset between the <svg> element and its containing <div>
584
//Finally, add any offset of the containing <div> on the whole page.
585
left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
586
top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
589
if (snapDistance && snapDistance > 0) {
590
top = Math.floor(top/snapDistance) * snapDistance;
593
nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
597
nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
599
nvtooltip.content = function(_) {
600
if (!arguments.length) return content;
605
//Returns tooltipElem...not able to set it.
606
nvtooltip.tooltipElem = function() {
610
nvtooltip.contentGenerator = function(_) {
611
if (!arguments.length) return contentGenerator;
612
if (typeof _ === 'function') {
613
contentGenerator = _;
618
nvtooltip.data = function(_) {
619
if (!arguments.length) return data;
624
nvtooltip.gravity = function(_) {
625
if (!arguments.length) return gravity;
630
nvtooltip.distance = function(_) {
631
if (!arguments.length) return distance;
636
nvtooltip.snapDistance = function(_) {
637
if (!arguments.length) return snapDistance;
642
nvtooltip.classes = function(_) {
643
if (!arguments.length) return classes;
648
nvtooltip.chartContainer = function(_) {
649
if (!arguments.length) return chartContainer;
654
nvtooltip.position = function(_) {
655
if (!arguments.length) return position;
656
position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
657
position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
661
nvtooltip.fixedTop = function(_) {
662
if (!arguments.length) return fixedTop;
667
nvtooltip.enabled = function(_) {
668
if (!arguments.length) return enabled;
673
nvtooltip.valueFormatter = function(_) {
674
if (!arguments.length) return valueFormatter;
675
if (typeof _ === 'function') {
681
nvtooltip.headerFormatter = function(_) {
682
if (!arguments.length) return headerFormatter;
683
if (typeof _ === 'function') {
689
//id() is a read-only function. You can't use it to set the id.
690
nvtooltip.id = function() {
699
//Original tooltip.show function. Kept for backward compatibility.
701
nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
703
//Create new tooltip div if it doesn't exist on DOM.
704
var container = document.createElement('div');
705
container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
707
var body = parentContainer;
708
if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
709
//If the parent element is an SVG element, place tooltip in the <body> element.
710
body = document.getElementsByTagName('body')[0];
713
container.style.left = 0;
714
container.style.top = 0;
715
container.style.opacity = 0;
716
container.innerHTML = content;
717
body.appendChild(container);
719
//If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
720
if (parentContainer) {
721
pos[0] = pos[0] - parentContainer.scrollLeft;
722
pos[1] = pos[1] - parentContainer.scrollTop;
724
nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
727
//Looks up the ancestry of a DOM element, and returns the first NON-svg node.
728
nv.tooltip.findFirstNonSVGParent = function(Elem) {
729
while(Elem.tagName.match(/^g|svg$/i) !== null) {
730
Elem = Elem.parentNode;
735
//Finds the total offsetTop of a given DOM element.
736
//Looks up the entire ancestry of an element, up to the first relatively positioned element.
737
nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
738
var offsetTop = initialTop;
741
if( !isNaN( Elem.offsetTop ) ) {
742
offsetTop += (Elem.offsetTop);
744
} while( Elem = Elem.offsetParent );
748
//Finds the total offsetLeft of a given DOM element.
749
//Looks up the entire ancestry of an element, up to the first relatively positioned element.
750
nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
751
var offsetLeft = initialLeft;
754
if( !isNaN( Elem.offsetLeft ) ) {
755
offsetLeft += (Elem.offsetLeft);
757
} while( Elem = Elem.offsetParent );
761
//Global utility function to render a tooltip on the DOM.
762
//pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
763
//gravity = how to orient the tooltip
764
//dist = how far away from the mouse to place tooltip
765
//container = tooltip DIV
766
nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
768
var height = parseInt(container.offsetHeight),
769
width = parseInt(container.offsetWidth),
770
windowWidth = nv.utils.windowSize().width,
771
windowHeight = nv.utils.windowSize().height,
772
scrollTop = window.pageYOffset,
773
scrollLeft = window.pageXOffset,
776
windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
777
windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
779
gravity = gravity || 's';
782
var tooltipTop = function ( Elem ) {
783
return nv.tooltip.findTotalOffsetTop(Elem, top);
786
var tooltipLeft = function ( Elem ) {
787
return nv.tooltip.findTotalOffsetLeft(Elem,left);
792
left = pos[0] - width - dist;
793
top = pos[1] - (height / 2);
794
var tLeft = tooltipLeft(container);
795
var tTop = tooltipTop(container);
796
if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
797
if (tTop < scrollTop) top = scrollTop - tTop + top;
798
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
801
left = pos[0] + dist;
802
top = pos[1] - (height / 2);
803
var tLeft = tooltipLeft(container);
804
var tTop = tooltipTop(container);
805
if (tLeft + width > windowWidth) left = pos[0] - width - dist;
806
if (tTop < scrollTop) top = scrollTop + 5;
807
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
810
left = pos[0] - (width / 2) - 5;
812
var tLeft = tooltipLeft(container);
813
var tTop = tooltipTop(container);
814
if (tLeft < scrollLeft) left = scrollLeft + 5;
815
if (tLeft + width > windowWidth) left = left - width/2 + 5;
816
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
819
left = pos[0] - (width / 2);
820
top = pos[1] - height - dist;
821
var tLeft = tooltipLeft(container);
822
var tTop = tooltipTop(container);
823
if (tLeft < scrollLeft) left = scrollLeft + 5;
824
if (tLeft + width > windowWidth) left = left - width/2 + 5;
825
if (scrollTop > tTop) top = scrollTop;
830
var tLeft = tooltipLeft(container);
831
var tTop = tooltipTop(container);
836
container.style.left = left+'px';
837
container.style.top = top+'px';
838
container.style.opacity = 1;
839
container.style.position = 'absolute';
844
//Global utility function to remove tooltips from the DOM.
845
nv.tooltip.cleanup = function() {
847
// Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
848
var tooltips = document.getElementsByClassName('nvtooltip');
850
while(tooltips.length) {
851
purging.push(tooltips[0]);
852
tooltips[0].style.transitionDelay = '0 !important';
853
tooltips[0].style.opacity = 0;
854
tooltips[0].className = 'nvtooltip-pending-removal';
857
setTimeout(function() {
859
while (purging.length) {
860
var removeMe = purging.pop();
861
removeMe.parentNode.removeChild(removeMe);
868
nv.utils.windowSize = function() {
870
var size = {width: 640, height: 480};
872
// Earlier IE uses Doc.body
873
if (document.body && document.body.offsetWidth) {
874
size.width = document.body.offsetWidth;
875
size.height = document.body.offsetHeight;
878
// IE can use depending on mode it is in
879
if (document.compatMode=='CSS1Compat' &&
880
document.documentElement &&
881
document.documentElement.offsetWidth ) {
882
size.width = document.documentElement.offsetWidth;
883
size.height = document.documentElement.offsetHeight;
886
// Most recent browsers use
887
if (window.innerWidth && window.innerHeight) {
888
size.width = window.innerWidth;
889
size.height = window.innerHeight;
896
// Easy way to bind multiple functions to window.onresize
897
// TODO: give a way to remove a function after its bound, other than removing all of them
898
nv.utils.windowResize = function(fun){
899
if (fun === undefined) return;
900
var oldresize = window.onresize;
902
window.onresize = function(e) {
903
if (typeof oldresize == 'function') oldresize(e);
908
// Backwards compatible way to implement more d3-like coloring of graphs.
909
// If passed an array, wrap it in a function which implements the old default
911
nv.utils.getColor = function(color) {
912
if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
914
if( Object.prototype.toString.call( color ) === '[object Array]' )
915
return function(d, i) { return d.color || color[i % color.length]; };
918
//can't really help it if someone passes rubbish as color
921
// Default color chooser uses the index of an object as before.
922
nv.utils.defaultColor = function() {
923
var colors = d3.scale.category20().range();
924
return function(d, i) { return d.color || colors[i % colors.length] };
928
// Returns a color function that takes the result of 'getKey' for each series and
929
// looks for a corresponding color from the dictionary,
930
nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
931
getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
932
defaultColors = defaultColors || d3.scale.category20().range(); //default color function
934
var defIndex = defaultColors.length; //current default color (going in reverse)
936
return function(series, index) {
937
var key = getKey(series);
939
if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
941
if (typeof dictionary[key] !== "undefined")
942
return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
944
return defaultColors[--defIndex]; // no match in dictionary, use default color
950
// From the PJAX example on d3js.org, while this is not really directly needed
951
// it's a very cool method for doing pjax, I may expand upon it a little bit,
952
// open to suggestions on anything that may be useful
953
nv.utils.pjax = function(links, content) {
954
d3.selectAll(links).on("click", function() {
955
history.pushState(this.href, this.textContent, this.href);
957
d3.event.preventDefault();
960
function load(href) {
961
d3.html(href, function(fragment) {
962
var target = d3.select(content).node();
963
target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
964
nv.utils.pjax(links, content);
968
d3.select(window).on("popstate", function() {
969
if (d3.event.state) load(d3.event.state);
973
/* For situations where we want to approximate the width in pixels for an SVG:text element.
974
Most common instance is when the element is in a display:none; container.
975
Forumla is : text.length * font-size * constant_factor
977
nv.utils.calcApproxTextWidth = function (svgTextElem) {
978
if (typeof svgTextElem.style === 'function'
979
&& typeof svgTextElem.text === 'function') {
980
var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
981
var textLength = svgTextElem.text().length;
983
return textLength * fontSize * 0.5;
988
/* Numbers that are undefined, null or NaN, convert them to zeros.
990
nv.utils.NaNtoZero = function(n) {
991
if (typeof n !== 'number'
994
|| n === Infinity) return 0;
1000
Snippet of code you can insert into each nv.models.* to give you the ability to
1007
To enable in the chart:
1008
chart.options = nv.utils.optionsFunc.bind(chart);
1010
nv.utils.optionsFunc = function(args) {
1012
d3.map(args).forEach((function(key,value) {
1013
if (typeof this[key] === "function") {
1019
};nv.models.axis = function() {
1021
//============================================================
1022
// Public Variables with Default Settings
1023
//------------------------------------------------------------
1025
var axis = d3.svg.axis()
1028
var margin = {top: 0, right: 0, bottom: 0, left: 0}
1029
, width = 75 //only used for tickLabel currently
1030
, height = 60 //only used for tickLabel currently
1031
, scale = d3.scale.linear()
1032
, axisLabelText = null
1033
, showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
1034
, highlightZero = true
1036
, rotateYLabel = true
1037
, staggerLabels = false
1040
, axisLabelDistance = 12 //The larger this number is, the closer the axis label is to the axis.
1046
.tickFormat(function(d) { return d })
1049
//============================================================
1052
//============================================================
1053
// Private Variables
1054
//------------------------------------------------------------
1058
//============================================================
1061
function chart(selection) {
1062
selection.each(function(data) {
1063
var container = d3.select(this);
1066
//------------------------------------------------------------
1067
// Setup containers and skeleton of chart
1069
var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
1070
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
1071
var gEnter = wrapEnter.append('g');
1072
var g = wrap.select('g')
1074
//------------------------------------------------------------
1079
else if (axis.orient() == 'top' || axis.orient() == 'bottom')
1080
axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
1083
//TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
1086
g.transition().call(axis);
1088
scale0 = scale0 || axis.scale();
1090
var fmt = axis.tickFormat();
1092
fmt = scale0.tickFormat();
1095
var axisLabel = g.selectAll('text.nv-axislabel')
1096
.data([axisLabelText || null]);
1097
axisLabel.exit().remove();
1098
switch (axis.orient()) {
1100
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1101
var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1103
.attr('text-anchor', 'middle')
1107
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1108
.data(scale.domain());
1109
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1110
axisMaxMin.exit().remove();
1112
.attr('transform', function(d,i) {
1113
return 'translate(' + scale(d) + ',0)'
1116
.attr('dy', '-0.5em')
1117
.attr('y', -axis.tickPadding())
1118
.attr('text-anchor', 'middle')
1119
.text(function(d,i) {
1121
return ('' + v).match('NaN') ? '' : v;
1123
axisMaxMin.transition()
1124
.attr('transform', function(d,i) {
1125
return 'translate(' + scale.range()[i] + ',0)'
1130
var xLabelMargin = 36;
1131
var maxTextWidth = 30;
1132
var xTicks = g.selectAll('g').select("text");
1133
if (rotateLabels%360) {
1134
//Calculate the longest xTick width
1135
xTicks.each(function(d,i){
1136
var width = this.getBBox().width;
1137
if(width > maxTextWidth) maxTextWidth = width;
1139
//Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1140
var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1141
var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1144
.attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1145
.style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1147
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1148
var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1150
.attr('text-anchor', 'middle')
1151
.attr('y', xLabelMargin)
1154
//if (showMaxMin && !isOrdinal) {
1155
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1156
//.data(scale.domain())
1157
.data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1158
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1159
axisMaxMin.exit().remove();
1161
.attr('transform', function(d,i) {
1162
return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1165
.attr('dy', '.71em')
1166
.attr('y', axis.tickPadding())
1167
.attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1168
.style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1169
.text(function(d,i) {
1171
return ('' + v).match('NaN') ? '' : v;
1173
axisMaxMin.transition()
1174
.attr('transform', function(d,i) {
1175
//return 'translate(' + scale.range()[i] + ',0)'
1176
//return 'translate(' + scale(d) + ',0)'
1177
return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1182
.attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
1186
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1188
.style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1189
.attr('transform', rotateYLabel ? 'rotate(90)' : '')
1190
.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
1191
.attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1193
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1194
.data(scale.domain());
1195
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1196
.style('opacity', 0);
1197
axisMaxMin.exit().remove();
1199
.attr('transform', function(d,i) {
1200
return 'translate(0,' + scale(d) + ')'
1203
.attr('dy', '.32em')
1205
.attr('x', axis.tickPadding())
1206
.style('text-anchor', 'start')
1207
.text(function(d,i) {
1209
return ('' + v).match('NaN') ? '' : v;
1211
axisMaxMin.transition()
1212
.attr('transform', function(d,i) {
1213
return 'translate(0,' + scale.range()[i] + ')'
1216
.style('opacity', 1);
1221
//For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1222
var yTicks = g.selectAll('g').select("text");
1223
yTicks.each(function(d,i){
1224
var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
1225
if(labelPadding > width) width = labelPadding;
1228
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1230
.style('text-anchor', rotateYLabel ? 'middle' : 'end')
1231
.attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1232
.attr('y', rotateYLabel ? (-Math.max(margin.left,width) + axisLabelDistance) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
1233
.attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1235
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1236
.data(scale.domain());
1237
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1238
.style('opacity', 0);
1239
axisMaxMin.exit().remove();
1241
.attr('transform', function(d,i) {
1242
return 'translate(0,' + scale0(d) + ')'
1245
.attr('dy', '.32em')
1247
.attr('x', -axis.tickPadding())
1248
.attr('text-anchor', 'end')
1249
.text(function(d,i) {
1251
return ('' + v).match('NaN') ? '' : v;
1253
axisMaxMin.transition()
1254
.attr('transform', function(d,i) {
1255
return 'translate(0,' + scale.range()[i] + ')'
1258
.style('opacity', 1);
1263
.text(function(d) { return d });
1266
if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1267
//check if max and min overlap other values, if so, hide the values that overlap
1268
g.selectAll('g') // the g's wrapping each tick
1269
.each(function(d,i) {
1270
d3.select(this).select('text').attr('opacity', 1);
1271
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!
1272
if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1273
d3.select(this).attr('opacity', 0);
1275
d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1279
//if Max and Min = 0 only show min, Issue #281
1280
if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
1281
wrap.selectAll('g.nv-axisMaxMin')
1282
.style('opacity', function(d,i) { return !i ? 1 : 0 });
1286
if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1287
var maxMinRange = [];
1288
wrap.selectAll('g.nv-axisMaxMin')
1289
.each(function(d,i) {
1291
if (i) // i== 1, max position
1292
maxMinRange.push(scale(d) - this.getBBox().width - 4) //assuming the max and min labels are as wide as the next tick (with an extra 4 pixels just in case)
1293
else // i==0, min position
1294
maxMinRange.push(scale(d) + this.getBBox().width + 4)
1296
if (i) // i== 1, max position
1297
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)
1298
else // i==0, min position
1299
maxMinRange.push(scale(d) + 4)
1302
g.selectAll('g') // the g's wrapping each tick
1303
.each(function(d,i) {
1304
if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1305
if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1306
d3.select(this).remove();
1308
d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1314
//highlight zero line ... Maybe should not be an option and should just be in CSS?
1316
g.selectAll('.tick')
1317
.filter(function(d) { return !parseFloat(Math.round(d.__data__*100000)/1000000) && (d.__data__ !== undefined) }) //this is because sometimes the 0 tick is a very small fraction, TODO: think of cleaner technique
1318
.classed('zero', true);
1320
//store old scales for use in transitions on update
1321
scale0 = scale.copy();
1329
//============================================================
1330
// Expose Public Variables
1331
//------------------------------------------------------------
1333
// expose chart's sub-components
1336
d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
1337
d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
1339
chart.options = nv.utils.optionsFunc.bind(chart);
1341
chart.margin = function(_) {
1342
if(!arguments.length) return margin;
1343
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1344
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1345
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1346
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1350
chart.width = function(_) {
1351
if (!arguments.length) return width;
1356
chart.ticks = function(_) {
1357
if (!arguments.length) return ticks;
1362
chart.height = function(_) {
1363
if (!arguments.length) return height;
1368
chart.axisLabel = function(_) {
1369
if (!arguments.length) return axisLabelText;
1374
chart.showMaxMin = function(_) {
1375
if (!arguments.length) return showMaxMin;
1380
chart.highlightZero = function(_) {
1381
if (!arguments.length) return highlightZero;
1386
chart.scale = function(_) {
1387
if (!arguments.length) return scale;
1390
isOrdinal = typeof scale.rangeBands === 'function';
1391
d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
1395
chart.rotateYLabel = function(_) {
1396
if(!arguments.length) return rotateYLabel;
1401
chart.rotateLabels = function(_) {
1402
if(!arguments.length) return rotateLabels;
1407
chart.staggerLabels = function(_) {
1408
if (!arguments.length) return staggerLabels;
1413
chart.axisLabelDistance = function(_) {
1414
if (!arguments.length) return axisLabelDistance;
1415
axisLabelDistance = _;
1419
//============================================================
1424
//TODO: consider deprecating and using multibar with single series for this
1425
nv.models.historicalBar = function() {
1427
//============================================================
1428
// Public Variables with Default Settings
1429
//------------------------------------------------------------
1431
var margin = {top: 0, right: 0, bottom: 0, left: 0}
1434
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
1435
, x = d3.scale.linear()
1436
, y = d3.scale.linear()
1437
, getX = function(d) { return d.x }
1438
, getY = function(d) { return d.y }
1443
, color = nv.utils.defaultColor()
1448
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
1449
, interactive = true
1452
//============================================================
1455
function chart(selection) {
1456
selection.each(function(data) {
1457
var availableWidth = width - margin.left - margin.right,
1458
availableHeight = height - margin.top - margin.bottom,
1459
container = d3.select(this);
1462
//------------------------------------------------------------
1465
x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
1468
x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
1470
x.range(xRange || [0, availableWidth]);
1472
y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
1473
.range(yRange || [availableHeight, 0]);
1475
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
1477
if (x.domain()[0] === x.domain()[1])
1479
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
1482
if (y.domain()[0] === y.domain()[1])
1484
y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
1487
//------------------------------------------------------------
1490
//------------------------------------------------------------
1491
// Setup containers and skeleton of chart
1493
var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
1494
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
1495
var defsEnter = wrapEnter.append('defs');
1496
var gEnter = wrapEnter.append('g');
1497
var g = wrap.select('g');
1499
gEnter.append('g').attr('class', 'nv-bars');
1501
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1503
//------------------------------------------------------------
1507
.on('click', function(d,i) {
1508
dispatch.chartClick({
1517
defsEnter.append('clipPath')
1518
.attr('id', 'nv-chart-clip-path-' + id)
1521
wrap.select('#nv-chart-clip-path-' + id + ' rect')
1522
.attr('width', availableWidth)
1523
.attr('height', availableHeight);
1525
g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
1529
var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
1530
.data(function(d) { return d }, function(d,i) {return getX(d,i)});
1532
bars.exit().remove();
1535
var barsEnter = bars.enter().append('rect')
1536
//.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
1538
.attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
1539
.attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
1540
.attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
1541
.on('mouseover', function(d,i) {
1542
if (!interactive) return;
1543
d3.select(this).classed('hover', true);
1544
dispatch.elementMouseover({
1547
pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
1554
.on('mouseout', function(d,i) {
1555
if (!interactive) return;
1556
d3.select(this).classed('hover', false);
1557
dispatch.elementMouseout({
1565
.on('click', function(d,i) {
1566
if (!interactive) return;
1567
dispatch.elementClick({
1572
pos: [x(getX(d,i)), y(getY(d,i))],
1576
d3.event.stopPropagation();
1578
.on('dblclick', function(d,i) {
1579
if (!interactive) return;
1580
dispatch.elementDblClick({
1585
pos: [x(getX(d,i)), y(getY(d,i))],
1589
d3.event.stopPropagation();
1593
.attr('fill', function(d,i) { return color(d, i); })
1594
.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
1596
.attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
1597
//TODO: better width calculations that don't assume always uniform data spacing;w
1598
.attr('width', (availableWidth / data[0].values.length) * .9 );
1602
.attr('y', function(d,i) {
1603
var rval = getY(d,i) < 0 ?
1605
y(0) - y(getY(d,i)) < 1 ?
1608
return nv.utils.NaNtoZero(rval);
1610
.attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
1617
//Create methods to allow outside functions to highlight a specific bar.
1618
chart.highlightPoint = function(pointIndex, isHoverOver) {
1619
d3.select(".nv-historicalBar-" + id)
1620
.select(".nv-bars .nv-bar-0-" + pointIndex)
1621
.classed("hover", isHoverOver)
1625
chart.clearHighlights = function() {
1626
d3.select(".nv-historicalBar-" + id)
1627
.select(".nv-bars .nv-bar.hover")
1628
.classed("hover", false)
1631
//============================================================
1632
// Expose Public Variables
1633
//------------------------------------------------------------
1635
chart.dispatch = dispatch;
1637
chart.options = nv.utils.optionsFunc.bind(chart);
1639
chart.x = function(_) {
1640
if (!arguments.length) return getX;
1645
chart.y = function(_) {
1646
if (!arguments.length) return getY;
1651
chart.margin = function(_) {
1652
if (!arguments.length) return margin;
1653
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1654
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1655
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1656
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1660
chart.width = function(_) {
1661
if (!arguments.length) return width;
1666
chart.height = function(_) {
1667
if (!arguments.length) return height;
1672
chart.xScale = function(_) {
1673
if (!arguments.length) return x;
1678
chart.yScale = function(_) {
1679
if (!arguments.length) return y;
1684
chart.xDomain = function(_) {
1685
if (!arguments.length) return xDomain;
1690
chart.yDomain = function(_) {
1691
if (!arguments.length) return yDomain;
1696
chart.xRange = function(_) {
1697
if (!arguments.length) return xRange;
1702
chart.yRange = function(_) {
1703
if (!arguments.length) return yRange;
1708
chart.forceX = function(_) {
1709
if (!arguments.length) return forceX;
1714
chart.forceY = function(_) {
1715
if (!arguments.length) return forceY;
1720
chart.padData = function(_) {
1721
if (!arguments.length) return padData;
1726
chart.clipEdge = function(_) {
1727
if (!arguments.length) return clipEdge;
1732
chart.color = function(_) {
1733
if (!arguments.length) return color;
1734
color = nv.utils.getColor(_);
1738
chart.id = function(_) {
1739
if (!arguments.length) return id;
1744
chart.interactive = function(_) {
1745
if(!arguments.length) return interactive;
1746
interactive = false;
1750
//============================================================
1756
// Chart design based on the recommendations of Stephen Few. Implementation
1757
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1758
// http://projects.instantcognition.com/protovis/bulletchart/
1760
nv.models.bullet = function() {
1762
//============================================================
1763
// Public Variables with Default Settings
1764
//------------------------------------------------------------
1766
var margin = {top: 0, right: 0, bottom: 0, left: 0}
1767
, orient = 'left' // TODO top & bottom
1769
, ranges = function(d) { return d.ranges }
1770
, markers = function(d) { return d.markers }
1771
, measures = function(d) { return d.measures }
1772
, rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1773
, markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
1774
, measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
1775
, forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1779
, color = nv.utils.getColor(['#1f77b4'])
1780
, dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1783
//============================================================
1786
function chart(selection) {
1787
selection.each(function(d, i) {
1788
var availableWidth = width - margin.left - margin.right,
1789
availableHeight = height - margin.top - margin.bottom,
1790
container = d3.select(this);
1792
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1793
markerz = markers.call(this, d, i).slice().sort(d3.descending),
1794
measurez = measures.call(this, d, i).slice().sort(d3.descending),
1795
rangeLabelz = rangeLabels.call(this, d, i).slice(),
1796
markerLabelz = markerLabels.call(this, d, i).slice(),
1797
measureLabelz = measureLabels.call(this, d, i).slice();
1800
//------------------------------------------------------------
1803
// Compute the new x-scale.
1804
var x1 = d3.scale.linear()
1805
.domain( d3.extent(d3.merge([forceX, rangez])) )
1806
.range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1808
// Retrieve the old x-scale, if this is an update.
1809
var x0 = this.__chart__ || d3.scale.linear()
1810
.domain([0, Infinity])
1813
// Stash the new scale.
1814
this.__chart__ = x1;
1817
var rangeMin = d3.min(rangez), //rangez[2]
1818
rangeMax = d3.max(rangez), //rangez[0]
1819
rangeAvg = rangez[1];
1821
//------------------------------------------------------------
1824
//------------------------------------------------------------
1825
// Setup containers and skeleton of chart
1827
var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1828
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1829
var gEnter = wrapEnter.append('g');
1830
var g = wrap.select('g');
1832
gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1833
gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1834
gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1835
gEnter.append('rect').attr('class', 'nv-measure');
1836
gEnter.append('path').attr('class', 'nv-markerTriangle');
1838
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1840
//------------------------------------------------------------
1844
var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1845
w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1846
var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1847
xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
1850
g.select('rect.nv-rangeMax')
1851
.attr('height', availableHeight)
1852
.attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
1853
.attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
1854
.datum(rangeMax > 0 ? rangeMax : rangeMin)
1856
.attr('x', rangeMin < 0 ?
1863
g.select('rect.nv-rangeAvg')
1864
.attr('height', availableHeight)
1865
.attr('width', w1(rangeAvg))
1866
.attr('x', xp1(rangeAvg))
1869
.attr('width', rangeMax <= 0 ?
1870
x1(rangeMax) - x1(rangeAvg)
1871
: x1(rangeAvg) - x1(rangeMin))
1872
.attr('x', rangeMax <= 0 ?
1877
g.select('rect.nv-rangeMin')
1878
.attr('height', availableHeight)
1879
.attr('width', w1(rangeMax))
1880
.attr('x', xp1(rangeMax))
1881
.attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1882
.attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1883
.datum(rangeMax > 0 ? rangeMin : rangeMax)
1885
.attr('width', rangeMax <= 0 ?
1886
x1(rangeAvg) - x1(rangeMin)
1887
: x1(rangeMax) - x1(rangeAvg))
1888
.attr('x', rangeMax <= 0 ?
1893
g.select('rect.nv-measure')
1894
.style('fill', color)
1895
.attr('height', availableHeight / 3)
1896
.attr('y', availableHeight / 3)
1897
.attr('width', measurez < 0 ?
1898
x1(0) - x1(measurez[0])
1899
: x1(measurez[0]) - x1(0))
1900
.attr('x', xp1(measurez))
1901
.on('mouseover', function() {
1902
dispatch.elementMouseover({
1904
label: measureLabelz[0] || 'Current',
1905
pos: [x1(measurez[0]), availableHeight/2]
1908
.on('mouseout', function() {
1909
dispatch.elementMouseout({
1911
label: measureLabelz[0] || 'Current'
1915
var h3 = availableHeight / 6;
1917
g.selectAll('path.nv-markerTriangle')
1918
.attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1919
.attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1920
.on('mouseover', function() {
1921
dispatch.elementMouseover({
1923
label: markerLabelz[0] || 'Previous',
1924
pos: [x1(markerz[0]), availableHeight/2]
1927
.on('mouseout', function() {
1928
dispatch.elementMouseout({
1930
label: markerLabelz[0] || 'Previous'
1934
g.selectAll('path.nv-markerTriangle').remove();
1938
wrap.selectAll('.nv-range')
1939
.on('mouseover', function(d,i) {
1940
var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1942
dispatch.elementMouseover({
1945
pos: [x1(d), availableHeight/2]
1948
.on('mouseout', function(d,i) {
1949
var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1951
dispatch.elementMouseout({
1957
/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
1958
// Update the range rects.
1959
var range = g.selectAll('rect.nv-range')
1962
range.enter().append('rect')
1963
.attr('class', function(d, i) { return 'nv-range nv-s' + i; })
1965
.attr('height', availableHeight)
1966
.attr('x', reverse ? x0 : 0)
1967
.on('mouseover', function(d,i) {
1968
dispatch.elementMouseover({
1970
label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
1971
pos: [x1(d), availableHeight/2]
1974
.on('mouseout', function(d,i) {
1975
dispatch.elementMouseout({
1977
label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
1981
d3.transition(range)
1982
.attr('x', reverse ? x1 : 0)
1984
.attr('height', availableHeight);
1987
// Update the measure rects.
1988
var measure = g.selectAll('rect.nv-measure')
1991
measure.enter().append('rect')
1992
.attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
1993
.style('fill', function(d,i) { return color(d,i ) })
1995
.attr('height', availableHeight / 3)
1996
.attr('x', reverse ? x0 : 0)
1997
.attr('y', availableHeight / 3)
1998
.on('mouseover', function(d) {
1999
dispatch.elementMouseover({
2001
label: 'Current', //TODO: make these labels a variable
2002
pos: [x1(d), availableHeight/2]
2005
.on('mouseout', function(d) {
2006
dispatch.elementMouseout({
2008
label: 'Current' //TODO: make these labels a variable
2012
d3.transition(measure)
2014
.attr('height', availableHeight / 3)
2015
.attr('x', reverse ? x1 : 0)
2016
.attr('y', availableHeight / 3);
2020
// Update the marker lines.
2021
var marker = g.selectAll('path.nv-markerTriangle')
2024
var h3 = availableHeight / 6;
2025
marker.enter().append('path')
2026
.attr('class', 'nv-markerTriangle')
2027
.attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
2028
.attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
2029
.on('mouseover', function(d,i) {
2030
dispatch.elementMouseover({
2033
pos: [x1(d), availableHeight/2]
2036
.on('mouseout', function(d,i) {
2037
dispatch.elementMouseout({
2043
d3.transition(marker)
2044
.attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
2046
marker.exit().remove();
2051
// d3.timer.flush(); // Not needed?
2057
//============================================================
2058
// Expose Public Variables
2059
//------------------------------------------------------------
2061
chart.dispatch = dispatch;
2063
chart.options = nv.utils.optionsFunc.bind(chart);
2065
// left, right, top, bottom
2066
chart.orient = function(_) {
2067
if (!arguments.length) return orient;
2069
reverse = orient == 'right' || orient == 'bottom';
2073
// ranges (bad, satisfactory, good)
2074
chart.ranges = function(_) {
2075
if (!arguments.length) return ranges;
2080
// markers (previous, goal)
2081
chart.markers = function(_) {
2082
if (!arguments.length) return markers;
2087
// measures (actual, forecast)
2088
chart.measures = function(_) {
2089
if (!arguments.length) return measures;
2094
chart.forceX = function(_) {
2095
if (!arguments.length) return forceX;
2100
chart.width = function(_) {
2101
if (!arguments.length) return width;
2106
chart.height = function(_) {
2107
if (!arguments.length) return height;
2112
chart.margin = function(_) {
2113
if (!arguments.length) return margin;
2114
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2115
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2116
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2117
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2121
chart.tickFormat = function(_) {
2122
if (!arguments.length) return tickFormat;
2127
chart.color = function(_) {
2128
if (!arguments.length) return color;
2129
color = nv.utils.getColor(_);
2133
//============================================================
2141
// Chart design based on the recommendations of Stephen Few. Implementation
2142
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
2143
// http://projects.instantcognition.com/protovis/bulletchart/
2144
nv.models.bulletChart = function() {
2146
//============================================================
2147
// Public Variables with Default Settings
2148
//------------------------------------------------------------
2150
var bullet = nv.models.bullet()
2153
var orient = 'left' // TODO top & bottom
2155
, margin = {top: 5, right: 40, bottom: 20, left: 120}
2156
, ranges = function(d) { return d.ranges }
2157
, markers = function(d) { return d.markers }
2158
, measures = function(d) { return d.measures }
2163
, tooltip = function(key, x, y, e, graph) {
2164
return '<h3>' + x + '</h3>' +
2167
, noData = 'No Data Available.'
2168
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
2171
//============================================================
2174
//============================================================
2175
// Private Variables
2176
//------------------------------------------------------------
2178
var showTooltip = function(e, offsetElement) {
2179
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
2180
top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
2181
content = tooltip(e.key, e.label, e.value, e, chart);
2183
nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
2186
//============================================================
2189
function chart(selection) {
2190
selection.each(function(d, i) {
2191
var container = d3.select(this);
2193
var availableWidth = (width || parseInt(container.style('width')) || 960)
2194
- margin.left - margin.right,
2195
availableHeight = height - margin.top - margin.bottom,
2199
chart.update = function() { chart(selection) };
2200
chart.container = this;
2202
//------------------------------------------------------------
2203
// Display No Data message if there's nothing to show.
2205
if (!d || !ranges.call(this, d, i)) {
2206
var noDataText = container.selectAll('.nv-noData').data([noData]);
2208
noDataText.enter().append('text')
2209
.attr('class', 'nvd3 nv-noData')
2210
.attr('dy', '-.7em')
2211
.style('text-anchor', 'middle');
2214
.attr('x', margin.left + availableWidth / 2)
2215
.attr('y', 18 + margin.top + availableHeight / 2)
2216
.text(function(d) { return d });
2220
container.selectAll('.nv-noData').remove();
2223
//------------------------------------------------------------
2227
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
2228
markerz = markers.call(this, d, i).slice().sort(d3.descending),
2229
measurez = measures.call(this, d, i).slice().sort(d3.descending);
2232
//------------------------------------------------------------
2233
// Setup containers and skeleton of chart
2235
var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
2236
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
2237
var gEnter = wrapEnter.append('g');
2238
var g = wrap.select('g');
2240
gEnter.append('g').attr('class', 'nv-bulletWrap');
2241
gEnter.append('g').attr('class', 'nv-titles');
2243
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2245
//------------------------------------------------------------
2248
// Compute the new x-scale.
2249
var x1 = d3.scale.linear()
2250
.domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
2251
.range(reverse ? [availableWidth, 0] : [0, availableWidth]);
2253
// Retrieve the old x-scale, if this is an update.
2254
var x0 = this.__chart__ || d3.scale.linear()
2255
.domain([0, Infinity])
2258
// Stash the new scale.
2259
this.__chart__ = x1;
2262
// Derive width-scales from the x-scales.
2263
var w0 = bulletWidth(x0),
2264
w1 = bulletWidth(x1);
2266
function bulletWidth(x) {
2268
return function(d) {
2269
return Math.abs(x(d) - x(0));
2273
function bulletTranslate(x) {
2274
return function(d) {
2275
return 'translate(' + x(d) + ',0)';
2280
var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
2281
w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
2284
var title = gEnter.select('.nv-titles').append('g')
2285
.attr('text-anchor', 'end')
2286
.attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
2287
title.append('text')
2288
.attr('class', 'nv-title')
2289
.text(function(d) { return d.title; });
2291
title.append('text')
2292
.attr('class', 'nv-subtitle')
2294
.text(function(d) { return d.subtitle; });
2299
.width(availableWidth)
2300
.height(availableHeight)
2302
var bulletWrap = g.select('.nv-bulletWrap');
2304
d3.transition(bulletWrap).call(bullet);
2308
// Compute the tick format.
2309
var format = tickFormat || x1.tickFormat( availableWidth / 100 );
2311
// Update the tick groups.
2312
var tick = g.selectAll('g.nv-tick')
2313
.data(x1.ticks( availableWidth / 50 ), function(d) {
2314
return this.textContent || format(d);
2317
// Initialize the ticks with the old scale, x0.
2318
var tickEnter = tick.enter().append('g')
2319
.attr('class', 'nv-tick')
2320
.attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
2321
.style('opacity', 1e-6);
2323
tickEnter.append('line')
2324
.attr('y1', availableHeight)
2325
.attr('y2', availableHeight * 7 / 6);
2327
tickEnter.append('text')
2328
.attr('text-anchor', 'middle')
2330
.attr('y', availableHeight * 7 / 6)
2334
// Transition the updating ticks to the new scale, x1.
2335
var tickUpdate = d3.transition(tick)
2336
.attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2337
.style('opacity', 1);
2339
tickUpdate.select('line')
2340
.attr('y1', availableHeight)
2341
.attr('y2', availableHeight * 7 / 6);
2343
tickUpdate.select('text')
2344
.attr('y', availableHeight * 7 / 6);
2346
// Transition the exiting ticks to the new scale, x1.
2347
d3.transition(tick.exit())
2348
.attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
2349
.style('opacity', 1e-6)
2353
//============================================================
2354
// Event Handling/Dispatching (in chart's scope)
2355
//------------------------------------------------------------
2357
dispatch.on('tooltipShow', function(e) {
2359
if (tooltips) showTooltip(e, that.parentNode);
2362
//============================================================
2372
//============================================================
2373
// Event Handling/Dispatching (out of chart's scope)
2374
//------------------------------------------------------------
2376
bullet.dispatch.on('elementMouseover.tooltip', function(e) {
2377
dispatch.tooltipShow(e);
2380
bullet.dispatch.on('elementMouseout.tooltip', function(e) {
2381
dispatch.tooltipHide(e);
2384
dispatch.on('tooltipHide', function() {
2385
if (tooltips) nv.tooltip.cleanup();
2388
//============================================================
2391
//============================================================
2392
// Expose Public Variables
2393
//------------------------------------------------------------
2395
chart.dispatch = dispatch;
2396
chart.bullet = bullet;
2398
d3.rebind(chart, bullet, 'color');
2400
chart.options = nv.utils.optionsFunc.bind(chart);
2402
// left, right, top, bottom
2403
chart.orient = function(x) {
2404
if (!arguments.length) return orient;
2406
reverse = orient == 'right' || orient == 'bottom';
2410
// ranges (bad, satisfactory, good)
2411
chart.ranges = function(x) {
2412
if (!arguments.length) return ranges;
2417
// markers (previous, goal)
2418
chart.markers = function(x) {
2419
if (!arguments.length) return markers;
2424
// measures (actual, forecast)
2425
chart.measures = function(x) {
2426
if (!arguments.length) return measures;
2431
chart.width = function(x) {
2432
if (!arguments.length) return width;
2437
chart.height = function(x) {
2438
if (!arguments.length) return height;
2443
chart.margin = function(_) {
2444
if (!arguments.length) return margin;
2445
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2446
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2447
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2448
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2452
chart.tickFormat = function(x) {
2453
if (!arguments.length) return tickFormat;
2458
chart.tooltips = function(_) {
2459
if (!arguments.length) return tooltips;
2464
chart.tooltipContent = function(_) {
2465
if (!arguments.length) return tooltip;
2470
chart.noData = function(_) {
2471
if (!arguments.length) return noData;
2476
//============================================================
2484
nv.models.cumulativeLineChart = function() {
2486
//============================================================
2487
// Public Variables with Default Settings
2488
//------------------------------------------------------------
2490
var lines = nv.models.line()
2491
, xAxis = nv.models.axis()
2492
, yAxis = nv.models.axis()
2493
, legend = nv.models.legend()
2494
, controls = nv.models.legend()
2495
, interactiveLayer = nv.interactiveGuideline()
2498
var margin = {top: 30, right: 30, bottom: 50, left: 60}
2499
, color = nv.utils.defaultColor()
2505
, rightAlignYAxis = false
2507
, showControls = true
2508
, useInteractiveGuideline = false
2510
, tooltip = function(key, x, y, e, graph) {
2511
return '<h3>' + key + '</h3>' +
2512
'<p>' + y + ' at ' + x + '</p>'
2514
, x //can be accessed via chart.xScale()
2515
, y //can be accessed via chart.yScale()
2517
, state = { index: 0, rescaleY: rescaleY }
2518
, defaultState = null
2519
, noData = 'No Data Available.'
2520
, average = function(d) { return d.average }
2521
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
2522
, transitionDuration = 250
2523
, noErrorCheck = false //if set to TRUE, will bypass an error check in the indexify function.
2531
.orient((rightAlignYAxis) ? 'right' : 'left')
2534
//============================================================
2535
controls.updateState(false);
2537
//============================================================
2538
// Private Variables
2539
//------------------------------------------------------------
2541
var dx = d3.scale.linear()
2542
, index = {i: 0, x: 0}
2545
var showTooltip = function(e, offsetElement) {
2546
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2547
top = e.pos[1] + ( offsetElement.offsetTop || 0),
2548
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2549
y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2550
content = tooltip(e.series.key, x, y, e, chart);
2552
nv.tooltip.show([left, top], content, null, null, offsetElement);
2555
//============================================================
2557
function chart(selection) {
2558
selection.each(function(data) {
2559
var container = d3.select(this).classed('nv-chart-' + id, true),
2562
var availableWidth = (width || parseInt(container.style('width')) || 960)
2563
- margin.left - margin.right,
2564
availableHeight = (height || parseInt(container.style('height')) || 400)
2565
- margin.top - margin.bottom;
2568
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
2569
chart.container = this;
2571
//set state.disabled
2572
state.disabled = data.map(function(d) { return !!d.disabled });
2574
if (!defaultState) {
2577
for (key in state) {
2578
if (state[key] instanceof Array)
2579
defaultState[key] = state[key].slice(0);
2581
defaultState[key] = state[key];
2585
var indexDrag = d3.behavior.drag()
2586
.on('dragstart', dragStart)
2587
.on('drag', dragMove)
2588
.on('dragend', dragEnd);
2591
function dragStart(d,i) {
2592
d3.select(chart.container)
2593
.style('cursor', 'ew-resize');
2596
function dragMove(d,i) {
2597
index.x = d3.event.x;
2598
index.i = Math.round(dx.invert(index.x));
2602
function dragEnd(d,i) {
2603
d3.select(chart.container)
2604
.style('cursor', 'auto');
2606
// update state and send stateChange with new index
2607
state.index = index.i;
2608
dispatch.stateChange(state);
2611
//------------------------------------------------------------
2612
// Display No Data message if there's nothing to show.
2614
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2615
var noDataText = container.selectAll('.nv-noData').data([noData]);
2617
noDataText.enter().append('text')
2618
.attr('class', 'nvd3 nv-noData')
2619
.attr('dy', '-.7em')
2620
.style('text-anchor', 'middle');
2623
.attr('x', margin.left + availableWidth / 2)
2624
.attr('y', margin.top + availableHeight / 2)
2625
.text(function(d) { return d });
2629
container.selectAll('.nv-noData').remove();
2632
//------------------------------------------------------------
2635
//------------------------------------------------------------
2643
var seriesDomains = data
2644
.filter(function(series) { return !series.disabled })
2645
.map(function(series,i) {
2646
var initialDomain = d3.extent(series.values, lines.y());
2648
//account for series being disabled when losing 95% or more
2649
if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2652
(initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2653
(initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2657
var completeDomain = [
2658
d3.min(seriesDomains, function(d) { return d[0] }),
2659
d3.max(seriesDomains, function(d) { return d[1] })
2662
lines.yDomain(completeDomain);
2664
lines.yDomain(null);
2668
dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length
2669
.range([0, availableWidth])
2672
//------------------------------------------------------------
2675
var data = indexify(index.i, data);
2678
//------------------------------------------------------------
2679
// Setup containers and skeleton of chart
2680
var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2681
var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2682
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2683
var g = wrap.select('g');
2685
gEnter.append('g').attr('class', 'nv-interactive');
2686
gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2687
gEnter.append('g').attr('class', 'nv-y nv-axis');
2688
gEnter.append('g').attr('class', 'nv-background');
2689
gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2690
gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2691
gEnter.append('g').attr('class', 'nv-legendWrap');
2692
gEnter.append('g').attr('class', 'nv-controlsWrap');
2695
//------------------------------------------------------------
2699
legend.width(availableWidth);
2701
g.select('.nv-legendWrap')
2705
if ( margin.top != legend.height()) {
2706
margin.top = legend.height();
2707
availableHeight = (height || parseInt(container.style('height')) || 400)
2708
- margin.top - margin.bottom;
2711
g.select('.nv-legendWrap')
2712
.attr('transform', 'translate(0,' + (-margin.top) +')')
2715
//------------------------------------------------------------
2718
//------------------------------------------------------------
2722
var controlsData = [
2723
{ key: 'Re-scale y-axis', disabled: !rescaleY }
2728
.color(['#444', '#444', '#444'])
2730
.margin({top: 5, right: 0, bottom: 5, left: 20})
2733
g.select('.nv-controlsWrap')
2734
.datum(controlsData)
2735
.attr('transform', 'translate(0,' + (-margin.top) +')')
2739
//------------------------------------------------------------
2742
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2744
if (rightAlignYAxis) {
2745
g.select(".nv-y.nv-axis")
2746
.attr("transform", "translate(" + availableWidth + ",0)");
2749
// Show error if series goes below 100%
2750
var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2752
wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2753
if (tempDisabled.length) {
2754
wrap.append('text').attr('class', 'tempDisabled')
2755
.attr('x', availableWidth / 2)
2756
.attr('y', '-.71em')
2757
.style('text-anchor', 'end')
2758
.text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2761
//------------------------------------------------------------
2762
// Main Chart Component(s)
2764
//------------------------------------------------------------
2765
//Set up interactive layer
2766
if (useInteractiveGuideline) {
2768
.width(availableWidth)
2769
.height(availableHeight)
2770
.margin({left:margin.left,top:margin.top})
2771
.svgContainer(container)
2773
wrap.select(".nv-interactive").call(interactiveLayer);
2776
gEnter.select('.nv-background')
2779
g.select('.nv-background rect')
2780
.attr('width', availableWidth)
2781
.attr('height', availableHeight);
2784
//.x(function(d) { return d.x })
2785
.y(function(d) { return d.display.y })
2786
.width(availableWidth)
2787
.height(availableHeight)
2788
.color(data.map(function(d,i) {
2789
return d.color || color(d, i);
2790
}).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2794
var linesWrap = g.select('.nv-linesWrap')
2795
.datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
2797
//d3.transition(linesWrap).call(lines);
2798
linesWrap.call(lines);
2800
/*Handle average lines [AN-612] ----------------------------*/
2802
//Store a series index number in the data array.
2803
data.forEach(function(d,i) {
2807
var avgLineData = data.filter(function(d) {
2808
return !d.disabled && !!average(d);
2811
var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2812
.data(avgLineData, function(d) { return d.key; });
2814
var getAvgLineY = function(d) {
2815
//If average lines go off the svg element, clamp them to the svg bounds.
2816
var yVal = y(average(d));
2817
if (yVal < 0) return 0;
2818
if (yVal > availableHeight) return availableHeight;
2824
.style('stroke-width',2)
2825
.style('stroke-dasharray','10,10')
2826
.style('stroke',function (d,i) {
2827
return lines.color()(d,d.seriesIndex);
2830
.attr('x2',availableWidth)
2831
.attr('y1', getAvgLineY)
2832
.attr('y2', getAvgLineY);
2835
.style('stroke-opacity',function(d){
2836
//If average lines go offscreen, make them transparent
2837
var yVal = y(average(d));
2838
if (yVal < 0 || yVal > availableHeight) return 0;
2842
.attr('x2',availableWidth)
2843
.attr('y1', getAvgLineY)
2844
.attr('y2', getAvgLineY);
2846
avgLines.exit().remove();
2848
//Create index line -----------------------------------------
2850
var indexLine = linesWrap.selectAll('.nv-indexLine')
2852
indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2855
.attr('fill', 'red')
2856
.attr('fill-opacity', .5)
2857
.style("pointer-events","all")
2861
.attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2862
.attr('height', availableHeight)
2864
//------------------------------------------------------------
2867
//------------------------------------------------------------
2873
//Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates)
2874
.ticks( Math.min(data[0].values.length,availableWidth/70) )
2875
.tickSize(-availableHeight, 0);
2877
g.select('.nv-x.nv-axis')
2878
.attr('transform', 'translate(0,' + y.range()[0] + ')');
2879
d3.transition(g.select('.nv-x.nv-axis'))
2887
.ticks( availableHeight / 36 )
2888
.tickSize( -availableWidth, 0);
2890
d3.transition(g.select('.nv-y.nv-axis'))
2893
//------------------------------------------------------------
2896
//============================================================
2897
// Event Handling/Dispatching (in chart's scope)
2898
//------------------------------------------------------------
2901
function updateZero() {
2905
//When dragging the index line, turn off line transitions.
2906
// Then turn them back on when done dragging.
2907
var oldDuration = chart.transitionDuration();
2908
chart.transitionDuration(0);
2910
chart.transitionDuration(oldDuration);
2913
g.select('.nv-background rect')
2914
.on('click', function() {
2915
index.x = d3.mouse(this)[0];
2916
index.i = Math.round(dx.invert(index.x));
2918
// update state and send stateChange with new index
2919
state.index = index.i;
2920
dispatch.stateChange(state);
2925
lines.dispatch.on('elementClick', function(e) {
2926
index.i = e.pointIndex;
2927
index.x = dx(index.i);
2929
// update state and send stateChange with new index
2930
state.index = index.i;
2931
dispatch.stateChange(state);
2936
controls.dispatch.on('legendClick', function(d,i) {
2937
d.disabled = !d.disabled;
2938
rescaleY = !d.disabled;
2940
state.rescaleY = rescaleY;
2941
dispatch.stateChange(state);
2946
legend.dispatch.on('stateChange', function(newState) {
2947
state.disabled = newState.disabled;
2948
dispatch.stateChange(state);
2952
interactiveLayer.dispatch.on('elementMousemove', function(e) {
2953
lines.clearHighlights();
2954
var singlePoint, pointIndex, pointXLocation, allData = [];
2958
.filter(function(series, i) {
2959
series.seriesIndex = i;
2960
return !series.disabled;
2962
.forEach(function(series,i) {
2963
pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2964
lines.highlightPoint(i, pointIndex, true);
2965
var point = series.values[pointIndex];
2966
if (typeof point === 'undefined') return;
2967
if (typeof singlePoint === 'undefined') singlePoint = point;
2968
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2971
value: chart.y()(point, pointIndex),
2972
color: color(series,series.seriesIndex)
2976
//Highlight the tooltip entry based on which point the mouse is closest to.
2977
if (allData.length > 2) {
2978
var yValue = chart.yScale().invert(e.mouseY);
2979
var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
2980
var threshold = 0.03 * domainExtent;
2981
var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
2982
if (indexToHighlight !== null)
2983
allData[indexToHighlight].highlight = true;
2986
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex), pointIndex);
2987
interactiveLayer.tooltip
2988
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2989
.chartContainer(that.parentNode)
2991
.valueFormatter(function(d,i) {
2992
return yAxis.tickFormat()(d);
3001
interactiveLayer.renderGuideLine(pointXLocation);
3005
interactiveLayer.dispatch.on("elementMouseout",function(e) {
3006
dispatch.tooltipHide();
3007
lines.clearHighlights();
3010
dispatch.on('tooltipShow', function(e) {
3011
if (tooltips) showTooltip(e, that.parentNode);
3015
// Update chart from a state object passed to event handler
3016
dispatch.on('changeState', function(e) {
3018
if (typeof e.disabled !== 'undefined') {
3019
data.forEach(function(series,i) {
3020
series.disabled = e.disabled[i];
3023
state.disabled = e.disabled;
3027
if (typeof e.index !== 'undefined') {
3029
index.x = dx(index.i);
3031
state.index = e.index;
3038
if (typeof e.rescaleY !== 'undefined') {
3039
rescaleY = e.rescaleY;
3045
//============================================================
3053
//============================================================
3054
// Event Handling/Dispatching (out of chart's scope)
3055
//------------------------------------------------------------
3057
lines.dispatch.on('elementMouseover.tooltip', function(e) {
3058
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3059
dispatch.tooltipShow(e);
3062
lines.dispatch.on('elementMouseout.tooltip', function(e) {
3063
dispatch.tooltipHide(e);
3066
dispatch.on('tooltipHide', function() {
3067
if (tooltips) nv.tooltip.cleanup();
3070
//============================================================
3073
//============================================================
3074
// Expose Public Variables
3075
//------------------------------------------------------------
3077
// expose chart's sub-components
3078
chart.dispatch = dispatch;
3079
chart.lines = lines;
3080
chart.legend = legend;
3081
chart.xAxis = xAxis;
3082
chart.yAxis = yAxis;
3083
chart.interactiveLayer = interactiveLayer;
3085
d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi', 'id');
3087
chart.options = nv.utils.optionsFunc.bind(chart);
3089
chart.margin = function(_) {
3090
if (!arguments.length) return margin;
3091
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3092
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3093
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3094
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3098
chart.width = function(_) {
3099
if (!arguments.length) return width;
3104
chart.height = function(_) {
3105
if (!arguments.length) return height;
3110
chart.color = function(_) {
3111
if (!arguments.length) return color;
3112
color = nv.utils.getColor(_);
3113
legend.color(color);
3117
chart.rescaleY = function(_) {
3118
if (!arguments.length) return rescaleY;
3123
chart.showControls = function(_) {
3124
if (!arguments.length) return showControls;
3129
chart.useInteractiveGuideline = function(_) {
3130
if(!arguments.length) return useInteractiveGuideline;
3131
useInteractiveGuideline = _;
3133
chart.interactive(false);
3134
chart.useVoronoi(false);
3139
chart.showLegend = function(_) {
3140
if (!arguments.length) return showLegend;
3145
chart.showXAxis = function(_) {
3146
if (!arguments.length) return showXAxis;
3151
chart.showYAxis = function(_) {
3152
if (!arguments.length) return showYAxis;
3157
chart.rightAlignYAxis = function(_) {
3158
if(!arguments.length) return rightAlignYAxis;
3159
rightAlignYAxis = _;
3160
yAxis.orient( (_) ? 'right' : 'left');
3164
chart.tooltips = function(_) {
3165
if (!arguments.length) return tooltips;
3170
chart.tooltipContent = function(_) {
3171
if (!arguments.length) return tooltip;
3176
chart.state = function(_) {
3177
if (!arguments.length) return state;
3182
chart.defaultState = function(_) {
3183
if (!arguments.length) return defaultState;
3188
chart.noData = function(_) {
3189
if (!arguments.length) return noData;
3194
chart.average = function(_) {
3195
if(!arguments.length) return average;
3200
chart.transitionDuration = function(_) {
3201
if (!arguments.length) return transitionDuration;
3202
transitionDuration = _;
3206
chart.noErrorCheck = function(_) {
3207
if (!arguments.length) return noErrorCheck;
3212
//============================================================
3215
//============================================================
3217
//------------------------------------------------------------
3219
/* Normalize the data according to an index point. */
3220
function indexify(idx, data) {
3221
return data.map(function(line, i) {
3225
var indexValue = line.values[idx];
3226
if (indexValue == null) {
3229
var v = lines.y()(indexValue, idx);
3231
//TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
3232
if (v < -.95 && !noErrorCheck) {
3233
//if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
3235
line.tempDisabled = true;
3239
line.tempDisabled = false;
3241
line.values = line.values.map(function(point, pointIndex) {
3242
point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
3250
//============================================================
3255
//TODO: consider deprecating by adding necessary features to multiBar model
3256
nv.models.discreteBar = function() {
3258
//============================================================
3259
// Public Variables with Default Settings
3260
//------------------------------------------------------------
3262
var margin = {top: 0, right: 0, bottom: 0, left: 0}
3265
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3266
, x = d3.scale.ordinal()
3267
, y = d3.scale.linear()
3268
, getX = function(d) { return d.x }
3269
, getY = function(d) { return d.y }
3270
, forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
3271
, color = nv.utils.defaultColor()
3272
, showValues = false
3273
, valueFormat = d3.format(',.2f')
3278
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
3279
, rectClass = 'discreteBar'
3282
//============================================================
3285
//============================================================
3286
// Private Variables
3287
//------------------------------------------------------------
3291
//============================================================
3294
function chart(selection) {
3295
selection.each(function(data) {
3296
var availableWidth = width - margin.left - margin.right,
3297
availableHeight = height - margin.top - margin.bottom,
3298
container = d3.select(this);
3301
//add series index to each data point for reference
3302
data.forEach(function(series, i) {
3303
series.values.forEach(function(point) {
3309
//------------------------------------------------------------
3312
// remap and flatten the data for use in calculating the scales' domains
3313
var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
3314
data.map(function(d) {
3315
return d.values.map(function(d,i) {
3316
return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
3320
x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
3321
.rangeBands(xRange || [0, availableWidth], .1);
3323
y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
3326
// If showValues, pad the Y axis range to account for label height
3327
if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
3328
else y.range(yRange || [availableHeight, 0]);
3330
//store old scales if they exist
3332
y0 = y0 || y.copy().range([y(0),y(0)]);
3334
//------------------------------------------------------------
3337
//------------------------------------------------------------
3338
// Setup containers and skeleton of chart
3340
var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
3341
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
3342
var gEnter = wrapEnter.append('g');
3343
var g = wrap.select('g');
3345
gEnter.append('g').attr('class', 'nv-groups');
3347
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3349
//------------------------------------------------------------
3353
//TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
3354
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
3355
.data(function(d) { return d }, function(d) { return d.key });
3356
groups.enter().append('g')
3357
.style('stroke-opacity', 1e-6)
3358
.style('fill-opacity', 1e-6);
3361
.style('stroke-opacity', 1e-6)
3362
.style('fill-opacity', 1e-6)
3365
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
3366
.classed('hover', function(d) { return d.hover });
3369
.style('stroke-opacity', 1)
3370
.style('fill-opacity', .75);
3373
var bars = groups.selectAll('g.nv-bar')
3374
.data(function(d) { return d.values });
3376
bars.exit().remove();
3379
var barsEnter = bars.enter().append('g')
3380
.attr('transform', function(d,i,j) {
3381
return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
3383
.on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
3384
d3.select(this).classed('hover', true);
3385
dispatch.elementMouseover({
3388
series: data[d.series],
3389
pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3391
seriesIndex: d.series,
3395
.on('mouseout', function(d,i) {
3396
d3.select(this).classed('hover', false);
3397
dispatch.elementMouseout({
3400
series: data[d.series],
3402
seriesIndex: d.series,
3406
.on('click', function(d,i) {
3407
dispatch.elementClick({
3410
series: data[d.series],
3411
pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3413
seriesIndex: d.series,
3416
d3.event.stopPropagation();
3418
.on('dblclick', function(d,i) {
3419
dispatch.elementDblClick({
3422
series: data[d.series],
3423
pos: [x(getX(d,i)) + (x.rangeBand() * (d.series + .5) / data.length), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3425
seriesIndex: d.series,
3428
d3.event.stopPropagation();
3431
barsEnter.append('rect')
3433
.attr('width', x.rangeBand() * .9 / data.length )
3436
barsEnter.append('text')
3437
.attr('text-anchor', 'middle')
3441
.text(function(d,i) { return valueFormat(getY(d,i)) })
3443
.attr('x', x.rangeBand() * .9 / 2)
3444
.attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3448
bars.selectAll('text').remove();
3452
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3453
.style('fill', function(d,i) { return d.color || color(d,i) })
3454
.style('stroke', function(d,i) { return d.color || color(d,i) })
3456
.attr('class', rectClass)
3458
.attr('width', x.rangeBand() * .9 / data.length);
3460
//.delay(function(d,i) { return i * 1200 / data[0].values.length })
3461
.attr('transform', function(d,i) {
3462
var left = x(getX(d,i)) + x.rangeBand() * .05,
3463
top = getY(d,i) < 0 ?
3465
y(0) - y(getY(d,i)) < 1 ?
3466
y(0) - 1 : //make 1 px positive bars show up above y=0
3469
return 'translate(' + left + ', ' + top + ')'
3472
.attr('height', function(d,i) {
3473
return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3477
//store old scales for use in transitions on update
3487
//============================================================
3488
// Expose Public Variables
3489
//------------------------------------------------------------
3491
chart.dispatch = dispatch;
3493
chart.options = nv.utils.optionsFunc.bind(chart);
3495
chart.x = function(_) {
3496
if (!arguments.length) return getX;
3501
chart.y = function(_) {
3502
if (!arguments.length) return getY;
3507
chart.margin = function(_) {
3508
if (!arguments.length) return margin;
3509
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3510
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3511
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3512
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3516
chart.width = function(_) {
3517
if (!arguments.length) return width;
3522
chart.height = function(_) {
3523
if (!arguments.length) return height;
3528
chart.xScale = function(_) {
3529
if (!arguments.length) return x;
3534
chart.yScale = function(_) {
3535
if (!arguments.length) return y;
3540
chart.xDomain = function(_) {
3541
if (!arguments.length) return xDomain;
3546
chart.yDomain = function(_) {
3547
if (!arguments.length) return yDomain;
3552
chart.xRange = function(_) {
3553
if (!arguments.length) return xRange;
3558
chart.yRange = function(_) {
3559
if (!arguments.length) return yRange;
3564
chart.forceY = function(_) {
3565
if (!arguments.length) return forceY;
3570
chart.color = function(_) {
3571
if (!arguments.length) return color;
3572
color = nv.utils.getColor(_);
3576
chart.id = function(_) {
3577
if (!arguments.length) return id;
3582
chart.showValues = function(_) {
3583
if (!arguments.length) return showValues;
3588
chart.valueFormat= function(_) {
3589
if (!arguments.length) return valueFormat;
3594
chart.rectClass= function(_) {
3595
if (!arguments.length) return rectClass;
3599
//============================================================
3605
nv.models.discreteBarChart = function() {
3607
//============================================================
3608
// Public Variables with Default Settings
3609
//------------------------------------------------------------
3611
var discretebar = nv.models.discreteBar()
3612
, xAxis = nv.models.axis()
3613
, yAxis = nv.models.axis()
3616
var margin = {top: 15, right: 10, bottom: 50, left: 60}
3619
, color = nv.utils.getColor()
3622
, rightAlignYAxis = false
3623
, staggerLabels = false
3625
, tooltip = function(key, x, y, e, graph) {
3626
return '<h3>' + x + '</h3>' +
3631
, noData = "No Data Available."
3632
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate')
3633
, transitionDuration = 250
3638
.highlightZero(false)
3640
.tickFormat(function(d) { return d })
3643
.orient((rightAlignYAxis) ? 'right' : 'left')
3644
.tickFormat(d3.format(',.1f'))
3647
//============================================================
3650
//============================================================
3651
// Private Variables
3652
//------------------------------------------------------------
3654
var showTooltip = function(e, offsetElement) {
3655
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3656
top = e.pos[1] + ( offsetElement.offsetTop || 0),
3657
x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3658
y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3659
content = tooltip(e.series.key, x, y, e, chart);
3661
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3664
//============================================================
3667
function chart(selection) {
3668
selection.each(function(data) {
3669
var container = d3.select(this),
3672
var availableWidth = (width || parseInt(container.style('width')) || 960)
3673
- margin.left - margin.right,
3674
availableHeight = (height || parseInt(container.style('height')) || 400)
3675
- margin.top - margin.bottom;
3678
chart.update = function() {
3679
dispatch.beforeUpdate();
3680
container.transition().duration(transitionDuration).call(chart);
3682
chart.container = this;
3685
//------------------------------------------------------------
3686
// Display No Data message if there's nothing to show.
3688
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3689
var noDataText = container.selectAll('.nv-noData').data([noData]);
3691
noDataText.enter().append('text')
3692
.attr('class', 'nvd3 nv-noData')
3693
.attr('dy', '-.7em')
3694
.style('text-anchor', 'middle');
3697
.attr('x', margin.left + availableWidth / 2)
3698
.attr('y', margin.top + availableHeight / 2)
3699
.text(function(d) { return d });
3703
container.selectAll('.nv-noData').remove();
3706
//------------------------------------------------------------
3709
//------------------------------------------------------------
3712
x = discretebar.xScale();
3713
y = discretebar.yScale().clamp(true);
3715
//------------------------------------------------------------
3718
//------------------------------------------------------------
3719
// Setup containers and skeleton of chart
3721
var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3722
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3723
var defsEnter = gEnter.append('defs');
3724
var g = wrap.select('g');
3726
gEnter.append('g').attr('class', 'nv-x nv-axis');
3727
gEnter.append('g').attr('class', 'nv-y nv-axis')
3728
.append('g').attr('class', 'nv-zeroLine')
3731
gEnter.append('g').attr('class', 'nv-barsWrap');
3733
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3735
if (rightAlignYAxis) {
3736
g.select(".nv-y.nv-axis")
3737
.attr("transform", "translate(" + availableWidth + ",0)");
3740
//------------------------------------------------------------
3743
//------------------------------------------------------------
3744
// Main Chart Component(s)
3747
.width(availableWidth)
3748
.height(availableHeight);
3751
var barsWrap = g.select('.nv-barsWrap')
3752
.datum(data.filter(function(d) { return !d.disabled }))
3754
barsWrap.transition().call(discretebar);
3756
//------------------------------------------------------------
3760
defsEnter.append('clipPath')
3761
.attr('id', 'nv-x-label-clip-' + discretebar.id())
3764
g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3765
.attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3767
.attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3770
//------------------------------------------------------------
3776
.ticks( availableWidth / 100 )
3777
.tickSize(-availableHeight, 0);
3779
g.select('.nv-x.nv-axis')
3780
.attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3781
//d3.transition(g.select('.nv-x.nv-axis'))
3782
g.select('.nv-x.nv-axis').transition()
3786
var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3788
if (staggerLabels) {
3791
.attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3798
.ticks( availableHeight / 36 )
3799
.tickSize( -availableWidth, 0);
3801
g.select('.nv-y.nv-axis').transition()
3806
g.select(".nv-zeroLine line")
3808
.attr("x2",availableWidth)
3813
//------------------------------------------------------------
3816
//============================================================
3817
// Event Handling/Dispatching (in chart's scope)
3818
//------------------------------------------------------------
3820
dispatch.on('tooltipShow', function(e) {
3821
if (tooltips) showTooltip(e, that.parentNode);
3824
//============================================================
3832
//============================================================
3833
// Event Handling/Dispatching (out of chart's scope)
3834
//------------------------------------------------------------
3836
discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3837
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3838
dispatch.tooltipShow(e);
3841
discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3842
dispatch.tooltipHide(e);
3845
dispatch.on('tooltipHide', function() {
3846
if (tooltips) nv.tooltip.cleanup();
3849
//============================================================
3852
//============================================================
3853
// Expose Public Variables
3854
//------------------------------------------------------------
3856
// expose chart's sub-components
3857
chart.dispatch = dispatch;
3858
chart.discretebar = discretebar;
3859
chart.xAxis = xAxis;
3860
chart.yAxis = yAxis;
3862
d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
3864
chart.options = nv.utils.optionsFunc.bind(chart);
3866
chart.margin = function(_) {
3867
if (!arguments.length) return margin;
3868
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3869
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3870
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3871
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3875
chart.width = function(_) {
3876
if (!arguments.length) return width;
3881
chart.height = function(_) {
3882
if (!arguments.length) return height;
3887
chart.color = function(_) {
3888
if (!arguments.length) return color;
3889
color = nv.utils.getColor(_);
3890
discretebar.color(color);
3894
chart.showXAxis = function(_) {
3895
if (!arguments.length) return showXAxis;
3900
chart.showYAxis = function(_) {
3901
if (!arguments.length) return showYAxis;
3906
chart.rightAlignYAxis = function(_) {
3907
if(!arguments.length) return rightAlignYAxis;
3908
rightAlignYAxis = _;
3909
yAxis.orient( (_) ? 'right' : 'left');
3913
chart.staggerLabels = function(_) {
3914
if (!arguments.length) return staggerLabels;
3919
chart.tooltips = function(_) {
3920
if (!arguments.length) return tooltips;
3925
chart.tooltipContent = function(_) {
3926
if (!arguments.length) return tooltip;
3931
chart.noData = function(_) {
3932
if (!arguments.length) return noData;
3937
chart.transitionDuration = function(_) {
3938
if (!arguments.length) return transitionDuration;
3939
transitionDuration = _;
3943
//============================================================
3949
nv.models.distribution = function() {
3951
//============================================================
3952
// Public Variables with Default Settings
3953
//------------------------------------------------------------
3955
var margin = {top: 0, right: 0, bottom: 0, left: 0}
3956
, width = 400 //technically width or height depending on x or y....
3958
, axis = 'x' // 'x' or 'y'... horizontal or vertical
3959
, getData = function(d) { return d[axis] } // defaults d.x or d.y
3960
, color = nv.utils.defaultColor()
3961
, scale = d3.scale.linear()
3965
//============================================================
3968
//============================================================
3969
// Private Variables
3970
//------------------------------------------------------------
3974
//============================================================
3977
function chart(selection) {
3978
selection.each(function(data) {
3979
var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
3980
naxis = axis == 'x' ? 'y' : 'x',
3981
container = d3.select(this);
3984
//------------------------------------------------------------
3987
scale0 = scale0 || scale;
3989
//------------------------------------------------------------
3992
//------------------------------------------------------------
3993
// Setup containers and skeleton of chart
3995
var wrap = container.selectAll('g.nv-distribution').data([data]);
3996
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
3997
var gEnter = wrapEnter.append('g');
3998
var g = wrap.select('g');
4000
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
4002
//------------------------------------------------------------
4005
var distWrap = g.selectAll('g.nv-dist')
4006
.data(function(d) { return d }, function(d) { return d.key });
4008
distWrap.enter().append('g');
4010
.attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
4011
.style('stroke', function(d,i) { return color(d, i) });
4013
var dist = distWrap.selectAll('line.nv-dist' + axis)
4014
.data(function(d) { return d.values })
4015
dist.enter().append('line')
4016
.attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
4017
.attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
4018
distWrap.exit().selectAll('line.nv-dist' + axis)
4020
.attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4021
.attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4022
.style('stroke-opacity', 0)
4025
.attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
4026
.attr(naxis + '1', 0)
4027
.attr(naxis + '2', size);
4030
.attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
4031
.attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
4034
scale0 = scale.copy();
4042
//============================================================
4043
// Expose Public Variables
4044
//------------------------------------------------------------
4045
chart.options = nv.utils.optionsFunc.bind(chart);
4047
chart.margin = function(_) {
4048
if (!arguments.length) return margin;
4049
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4050
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4051
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4052
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4056
chart.width = function(_) {
4057
if (!arguments.length) return width;
4062
chart.axis = function(_) {
4063
if (!arguments.length) return axis;
4068
chart.size = function(_) {
4069
if (!arguments.length) return size;
4074
chart.getData = function(_) {
4075
if (!arguments.length) return getData;
4076
getData = d3.functor(_);
4080
chart.scale = function(_) {
4081
if (!arguments.length) return scale;
4086
chart.color = function(_) {
4087
if (!arguments.length) return color;
4088
color = nv.utils.getColor(_);
4091
//============================================================
4097
nv.models.historicalBarChart = function() {
4099
//============================================================
4100
// Public Variables with Default Settings
4101
//------------------------------------------------------------
4103
var bars = nv.models.historicalBar()
4104
, xAxis = nv.models.axis()
4105
, yAxis = nv.models.axis()
4106
, legend = nv.models.legend()
4110
var margin = {top: 30, right: 90, bottom: 50, left: 90}
4111
, color = nv.utils.defaultColor()
4114
, showLegend = false
4117
, rightAlignYAxis = false
4119
, tooltip = function(key, x, y, e, graph) {
4120
return '<h3>' + key + '</h3>' +
4121
'<p>' + y + ' at ' + x + '</p>'
4126
, defaultState = null
4127
, noData = 'No Data Available.'
4128
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4129
, transitionDuration = 250
4137
.orient( (rightAlignYAxis) ? 'right' : 'left')
4140
//============================================================
4143
//============================================================
4144
// Private Variables
4145
//------------------------------------------------------------
4147
var showTooltip = function(e, offsetElement) {
4149
// New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4150
if (offsetElement) {
4151
var svg = d3.select(offsetElement).select('svg');
4152
var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4154
viewBox = viewBox.split(' ');
4155
var ratio = parseInt(svg.style('width')) / viewBox[2];
4156
e.pos[0] = e.pos[0] * ratio;
4157
e.pos[1] = e.pos[1] * ratio;
4161
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4162
top = e.pos[1] + ( offsetElement.offsetTop || 0),
4163
x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
4164
y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
4165
content = tooltip(e.series.key, x, y, e, chart);
4167
nv.tooltip.show([left, top], content, null, null, offsetElement);
4170
//============================================================
4173
function chart(selection) {
4174
selection.each(function(data) {
4175
var container = d3.select(this),
4178
var availableWidth = (width || parseInt(container.style('width')) || 960)
4179
- margin.left - margin.right,
4180
availableHeight = (height || parseInt(container.style('height')) || 400)
4181
- margin.top - margin.bottom;
4184
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
4185
chart.container = this;
4187
//set state.disabled
4188
state.disabled = data.map(function(d) { return !!d.disabled });
4190
if (!defaultState) {
4193
for (key in state) {
4194
if (state[key] instanceof Array)
4195
defaultState[key] = state[key].slice(0);
4197
defaultState[key] = state[key];
4201
//------------------------------------------------------------
4202
// Display noData message if there's nothing to show.
4204
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4205
var noDataText = container.selectAll('.nv-noData').data([noData]);
4207
noDataText.enter().append('text')
4208
.attr('class', 'nvd3 nv-noData')
4209
.attr('dy', '-.7em')
4210
.style('text-anchor', 'middle');
4213
.attr('x', margin.left + availableWidth / 2)
4214
.attr('y', margin.top + availableHeight / 2)
4215
.text(function(d) { return d });
4219
container.selectAll('.nv-noData').remove();
4222
//------------------------------------------------------------
4225
//------------------------------------------------------------
4231
//------------------------------------------------------------
4234
//------------------------------------------------------------
4235
// Setup containers and skeleton of chart
4237
var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
4238
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
4239
var g = wrap.select('g');
4241
gEnter.append('g').attr('class', 'nv-x nv-axis');
4242
gEnter.append('g').attr('class', 'nv-y nv-axis');
4243
gEnter.append('g').attr('class', 'nv-barsWrap');
4244
gEnter.append('g').attr('class', 'nv-legendWrap');
4246
//------------------------------------------------------------
4249
//------------------------------------------------------------
4253
legend.width(availableWidth);
4255
g.select('.nv-legendWrap')
4259
if ( margin.top != legend.height()) {
4260
margin.top = legend.height();
4261
availableHeight = (height || parseInt(container.style('height')) || 400)
4262
- margin.top - margin.bottom;
4265
wrap.select('.nv-legendWrap')
4266
.attr('transform', 'translate(0,' + (-margin.top) +')')
4269
//------------------------------------------------------------
4271
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4273
if (rightAlignYAxis) {
4274
g.select(".nv-y.nv-axis")
4275
.attr("transform", "translate(" + availableWidth + ",0)");
4279
//------------------------------------------------------------
4280
// Main Chart Component(s)
4283
.width(availableWidth)
4284
.height(availableHeight)
4285
.color(data.map(function(d,i) {
4286
return d.color || color(d, i);
4287
}).filter(function(d,i) { return !data[i].disabled }));
4290
var barsWrap = g.select('.nv-barsWrap')
4291
.datum(data.filter(function(d) { return !d.disabled }))
4293
barsWrap.transition().call(bars);
4295
//------------------------------------------------------------
4298
//------------------------------------------------------------
4304
.tickSize(-availableHeight, 0);
4306
g.select('.nv-x.nv-axis')
4307
.attr('transform', 'translate(0,' + y.range()[0] + ')');
4308
g.select('.nv-x.nv-axis')
4316
.ticks( availableHeight / 36 )
4317
.tickSize( -availableWidth, 0);
4319
g.select('.nv-y.nv-axis')
4323
//------------------------------------------------------------
4326
//============================================================
4327
// Event Handling/Dispatching (in chart's scope)
4328
//------------------------------------------------------------
4330
legend.dispatch.on('legendClick', function(d,i) {
4331
d.disabled = !d.disabled;
4333
if (!data.filter(function(d) { return !d.disabled }).length) {
4334
data.map(function(d) {
4336
wrap.selectAll('.nv-series').classed('disabled', false);
4341
state.disabled = data.map(function(d) { return !!d.disabled });
4342
dispatch.stateChange(state);
4344
selection.transition().call(chart);
4347
legend.dispatch.on('legendDblclick', function(d) {
4348
//Double clicking should always enable current series, and disabled all others.
4349
data.forEach(function(d) {
4354
state.disabled = data.map(function(d) { return !!d.disabled });
4355
dispatch.stateChange(state);
4359
dispatch.on('tooltipShow', function(e) {
4360
if (tooltips) showTooltip(e, that.parentNode);
4364
dispatch.on('changeState', function(e) {
4366
if (typeof e.disabled !== 'undefined') {
4367
data.forEach(function(series,i) {
4368
series.disabled = e.disabled[i];
4371
state.disabled = e.disabled;
4377
//============================================================
4385
//============================================================
4386
// Event Handling/Dispatching (out of chart's scope)
4387
//------------------------------------------------------------
4389
bars.dispatch.on('elementMouseover.tooltip', function(e) {
4390
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
4391
dispatch.tooltipShow(e);
4394
bars.dispatch.on('elementMouseout.tooltip', function(e) {
4395
dispatch.tooltipHide(e);
4398
dispatch.on('tooltipHide', function() {
4399
if (tooltips) nv.tooltip.cleanup();
4402
//============================================================
4405
//============================================================
4406
// Expose Public Variables
4407
//------------------------------------------------------------
4409
// expose chart's sub-components
4410
chart.dispatch = dispatch;
4412
chart.legend = legend;
4413
chart.xAxis = xAxis;
4414
chart.yAxis = yAxis;
4416
d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale',
4417
'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights', 'interactive');
4419
chart.options = nv.utils.optionsFunc.bind(chart);
4421
chart.margin = function(_) {
4422
if (!arguments.length) return margin;
4423
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4424
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4425
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4426
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4430
chart.width = function(_) {
4431
if (!arguments.length) return width;
4436
chart.height = function(_) {
4437
if (!arguments.length) return height;
4442
chart.color = function(_) {
4443
if (!arguments.length) return color;
4444
color = nv.utils.getColor(_);
4445
legend.color(color);
4449
chart.showLegend = function(_) {
4450
if (!arguments.length) return showLegend;
4455
chart.showXAxis = function(_) {
4456
if (!arguments.length) return showXAxis;
4461
chart.showYAxis = function(_) {
4462
if (!arguments.length) return showYAxis;
4467
chart.rightAlignYAxis = function(_) {
4468
if(!arguments.length) return rightAlignYAxis;
4469
rightAlignYAxis = _;
4470
yAxis.orient( (_) ? 'right' : 'left');
4474
chart.tooltips = function(_) {
4475
if (!arguments.length) return tooltips;
4480
chart.tooltipContent = function(_) {
4481
if (!arguments.length) return tooltip;
4486
chart.state = function(_) {
4487
if (!arguments.length) return state;
4492
chart.defaultState = function(_) {
4493
if (!arguments.length) return defaultState;
4498
chart.noData = function(_) {
4499
if (!arguments.length) return noData;
4504
chart.transitionDuration = function(_) {
4505
if (!arguments.length) return transitionDuration;
4506
transitionDuration = _;
4510
//============================================================
4515
nv.models.indentedTree = function() {
4517
//============================================================
4518
// Public Variables with Default Settings
4519
//------------------------------------------------------------
4521
var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
4524
, color = nv.utils.defaultColor()
4525
, id = Math.floor(Math.random() * 10000)
4527
, filterZero = false
4528
, noData = "No Data Available."
4530
, columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
4532
, iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images
4533
, iconClose = 'images/grey-minus.png'
4534
, dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout')
4535
, getUrl = function(d) { return d.url }
4538
//============================================================
4542
function chart(selection) {
4543
selection.each(function(data) {
4545
container = d3.select(this);
4547
var tree = d3.layout.tree()
4548
.children(function(d) { return d.values })
4549
.size([height, childIndent]); //Not sure if this is needed now that the result is HTML
4551
chart.update = function() { container.transition().duration(600).call(chart) };
4554
//------------------------------------------------------------
4555
// Display No Data message if there's nothing to show.
4556
if (!data[0]) data[0] = {key: noData};
4558
//------------------------------------------------------------
4561
var nodes = tree.nodes(data[0]);
4563
// nodes.map(function(d) {
4567
//------------------------------------------------------------
4568
// Setup containers and skeleton of chart
4570
var wrap = d3.select(this).selectAll('div').data([[nodes]]);
4571
var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree');
4572
var tableEnter = wrapEnter.append('table');
4573
var table = wrap.select('table').attr('width', '100%').attr('class', tableClass);
4575
//------------------------------------------------------------
4579
var thead = tableEnter.append('thead');
4581
var theadRow1 = thead.append('tr');
4583
columns.forEach(function(column) {
4586
.attr('width', column.width ? column.width : '10%')
4587
.style('text-align', column.type == 'numeric' ? 'right' : 'left')
4589
.text(column.label);
4594
var tbody = table.selectAll('tbody')
4595
.data(function(d) { return d });
4596
tbody.enter().append('tbody');
4600
//compute max generations
4601
depth = d3.max(nodes, function(node) { return node.depth });
4602
tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all
4605
// Update the nodes…
4606
var node = tbody.selectAll('tr')
4607
// .data(function(d) { return d; }, function(d) { return d.id || (d.id == ++i)});
4608
.data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) : true; } )}, function(d,i) { return d.id || (d.id || ++idx)});
4609
//.style('display', 'table-row'); //TODO: see if this does anything
4611
node.exit().remove();
4613
node.select('img.nv-treeicon')
4615
.classed('folded', folded);
4617
var nodeEnter = node.enter().append('tr');
4620
columns.forEach(function(column, index) {
4622
var nodeName = nodeEnter.append('td')
4623
.style('padding-left', function(d) { return (index ? 0 : d.depth * childIndent + 12 + (icon(d) ? 0 : 16)) + 'px' }, 'important') //TODO: check why I did the ternary here
4624
.style('text-align', column.type == 'numeric' ? 'right' : 'left');
4628
nodeName.append('img')
4629
.classed('nv-treeicon', true)
4630
.classed('nv-folded', folded)
4632
.style('width', '14px')
4633
.style('height', '14px')
4634
.style('padding', '0 1px')
4635
.style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; })
4636
.on('click', click);
4640
nodeName.each(function(d) {
4641
if (!index && getUrl(d))
4644
.attr('href',getUrl)
4645
.attr('class', d3.functor(column.classes))
4651
d3.select(this).select('span')
4652
.attr('class', d3.functor(column.classes) )
4653
.text(function(d) { return column.format ? (d[column.key] ? column.format(d[column.key]) : '-') : (d[column.key] || '-'); });
4656
if (column.showCount) {
4657
nodeName.append('span')
4658
.attr('class', 'nv-childrenCount');
4660
node.selectAll('span.nv-childrenCount').text(function(d) {
4661
return ((d.values && d.values.length) || (d._values && d._values.length)) ? //If this is a parent
4662
'(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length)) //If children are in values check its children and filter
4663
|| (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length) //Otherwise, do the same, but with the other name, _values...
4664
|| 0) + ')' //This is the catch-all in case there are no children after a filter
4665
: '' //If this is not a parent, just give an empty string
4669
// if (column.click)
4670
// nodeName.select('span').on('click', column.click);
4676
.on('click', function(d) {
4677
dispatch.elementClick({
4678
row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href)
4683
.on('dblclick', function(d) {
4684
dispatch.elementDblclick({
4690
.on('mouseover', function(d) {
4691
dispatch.elementMouseover({
4697
.on('mouseout', function(d) {
4698
dispatch.elementMouseout({
4708
// Toggle children on click.
4709
function click(d, _, unshift) {
4710
d3.event.stopPropagation();
4712
if(d3.event.shiftKey && !unshift) {
4713
//If you shift-click, it'll toggle fold all the children, instead of itself
4714
d3.event.shiftKey = false;
4715
d.values && d.values.forEach(function(node){
4716
if (node.values || node._values) {
4717
click(node, 0, true);
4722
if(!hasChildren(d)) {
4724
//window.location.href = d.url;
4728
d._values = d.values;
4731
d.values = d._values;
4739
return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
4742
function folded(d) {
4743
return (d._values && d._values.length);
4746
function hasChildren(d) {
4747
var values = d.values || d._values;
4749
return (values && values.length);
4759
//============================================================
4760
// Expose Public Variables
4761
//------------------------------------------------------------
4762
chart.options = nv.utils.optionsFunc.bind(chart);
4764
chart.margin = function(_) {
4765
if (!arguments.length) return margin;
4766
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4767
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4768
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4769
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4773
chart.width = function(_) {
4774
if (!arguments.length) return width;
4779
chart.height = function(_) {
4780
if (!arguments.length) return height;
4785
chart.color = function(_) {
4786
if (!arguments.length) return color;
4787
color = nv.utils.getColor(_);
4788
scatter.color(color);
4792
chart.id = function(_) {
4793
if (!arguments.length) return id;
4798
chart.header = function(_) {
4799
if (!arguments.length) return header;
4804
chart.noData = function(_) {
4805
if (!arguments.length) return noData;
4810
chart.filterZero = function(_) {
4811
if (!arguments.length) return filterZero;
4816
chart.columns = function(_) {
4817
if (!arguments.length) return columns;
4822
chart.tableClass = function(_) {
4823
if (!arguments.length) return tableClass;
4828
chart.iconOpen = function(_){
4829
if (!arguments.length) return iconOpen;
4834
chart.iconClose = function(_){
4835
if (!arguments.length) return iconClose;
4840
chart.getUrl = function(_){
4841
if (!arguments.length) return getUrl;
4846
//============================================================
4850
};nv.models.legend = function() {
4852
//============================================================
4853
// Public Variables with Default Settings
4854
//------------------------------------------------------------
4856
var margin = {top: 5, right: 0, bottom: 5, left: 0}
4859
, getKey = function(d) { return d.key }
4860
, color = nv.utils.defaultColor()
4863
, updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4864
, radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4865
, dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4868
//============================================================
4871
function chart(selection) {
4872
selection.each(function(data) {
4873
var availableWidth = width - margin.left - margin.right,
4874
container = d3.select(this);
4877
//------------------------------------------------------------
4878
// Setup containers and skeleton of chart
4880
var wrap = container.selectAll('g.nv-legend').data([data]);
4881
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4882
var g = wrap.select('g');
4884
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4886
//------------------------------------------------------------
4889
var series = g.selectAll('.nv-series')
4890
.data(function(d) { return d });
4891
var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4892
.on('mouseover', function(d,i) {
4893
dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
4895
.on('mouseout', function(d,i) {
4896
dispatch.legendMouseout(d,i);
4898
.on('click', function(d,i) {
4899
dispatch.legendClick(d,i);
4901
if (radioButtonMode) {
4902
//Radio button mode: set every series to disabled,
4903
// and enable the clicked series.
4904
data.forEach(function(series) { series.disabled = true});
4908
d.disabled = !d.disabled;
4909
if (data.every(function(series) { return series.disabled})) {
4910
//the default behavior of NVD3 legends is, if every single series
4911
// is disabled, turn all series' back on.
4912
data.forEach(function(series) { series.disabled = false});
4915
dispatch.stateChange({
4916
disabled: data.map(function(d) { return !!d.disabled })
4920
.on('dblclick', function(d,i) {
4921
dispatch.legendDblclick(d,i);
4923
//the default behavior of NVD3 legends, when double clicking one,
4924
// is to set all other series' to false, and make the double clicked series enabled.
4925
data.forEach(function(series) {
4926
series.disabled = true;
4929
dispatch.stateChange({
4930
disabled: data.map(function(d) { return !!d.disabled })
4934
seriesEnter.append('circle')
4935
.style('stroke-width', 2)
4936
.attr('class','nv-legend-symbol')
4938
seriesEnter.append('text')
4939
.attr('text-anchor', 'start')
4940
.attr('class','nv-legend-text')
4941
.attr('dy', '.32em')
4943
series.classed('disabled', function(d) { return d.disabled });
4944
series.exit().remove();
4945
series.select('circle')
4946
.style('fill', function(d,i) { return d.color || color(d,i)})
4947
.style('stroke', function(d,i) { return d.color || color(d, i) });
4948
series.select('text').text(getKey);
4951
//TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4953
// NEW ALIGNING CODE, TODO: clean up
4956
var seriesWidths = [];
4957
series.each(function(d,i) {
4958
var legendText = d3.select(this).select('text');
4961
nodeTextLength = legendText.getComputedTextLength();
4962
// If the legendText is display:none'd (nodeTextLength == 0), simulate an error so we approximate, instead
4963
if(nodeTextLength <= 0) throw Error();
4966
nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4969
seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
4972
var seriesPerRow = 0;
4973
var legendWidth = 0;
4974
var columnWidths = [];
4976
while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4977
columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4978
legendWidth += seriesWidths[seriesPerRow++];
4980
if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4983
while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4987
for (var k = 0; k < seriesWidths.length; k++) {
4988
if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4989
columnWidths[k % seriesPerRow] = seriesWidths[k];
4992
legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4997
var xPositions = [];
4998
for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4999
xPositions[i] = curX;
5000
curX += columnWidths[i];
5004
.attr('transform', function(d, i) {
5005
return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
5008
//position legend as far right as possible within the total width
5010
g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
5013
g.attr('transform', 'translate(0' + ',' + margin.top + ')');
5016
height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
5025
.attr('transform', function(d, i) {
5026
var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
5029
if (width < margin.left + margin.right + xpos + length) {
5035
if (newxpos > maxwidth) maxwidth = newxpos;
5037
return 'translate(' + xpos + ',' + ypos + ')';
5040
//position legend as far right as possible within the total width
5041
g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
5043
height = margin.top + margin.bottom + ypos + 15;
5053
//============================================================
5054
// Expose Public Variables
5055
//------------------------------------------------------------
5057
chart.dispatch = dispatch;
5058
chart.options = nv.utils.optionsFunc.bind(chart);
5060
chart.margin = function(_) {
5061
if (!arguments.length) return margin;
5062
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5063
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5064
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5065
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5069
chart.width = function(_) {
5070
if (!arguments.length) return width;
5075
chart.height = function(_) {
5076
if (!arguments.length) return height;
5081
chart.key = function(_) {
5082
if (!arguments.length) return getKey;
5087
chart.color = function(_) {
5088
if (!arguments.length) return color;
5089
color = nv.utils.getColor(_);
5093
chart.align = function(_) {
5094
if (!arguments.length) return align;
5099
chart.rightAlign = function(_) {
5100
if (!arguments.length) return rightAlign;
5105
chart.updateState = function(_) {
5106
if (!arguments.length) return updateState;
5111
chart.radioButtonMode = function(_) {
5112
if (!arguments.length) return radioButtonMode;
5113
radioButtonMode = _;
5117
//============================================================
5123
nv.models.line = function() {
5125
//============================================================
5126
// Public Variables with Default Settings
5127
//------------------------------------------------------------
5129
var scatter = nv.models.scatter()
5132
var margin = {top: 0, right: 0, bottom: 0, left: 0}
5135
, color = nv.utils.defaultColor() // a function that returns a color
5136
, getX = function(d) { return d.x } // accessor to get the x value from a data point
5137
, getY = function(d) { return d.y } // accessor to get the y value from a data point
5138
, 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
5139
, isArea = function(d) { return d.area } // decides if a line is an area or just a line
5140
, clipEdge = false // if true, masks lines within x and y scale
5141
, x //can be accessed via chart.xScale()
5142
, y //can be accessed via chart.yScale()
5143
, interpolate = "linear" // controls the line interpolation
5147
.size(16) // default size
5148
.sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5151
//============================================================
5154
//============================================================
5155
// Private Variables
5156
//------------------------------------------------------------
5158
var x0, y0 //used to store previous scales
5161
//============================================================
5164
function chart(selection) {
5165
selection.each(function(data) {
5166
var availableWidth = width - margin.left - margin.right,
5167
availableHeight = height - margin.top - margin.bottom,
5168
container = d3.select(this);
5170
//------------------------------------------------------------
5173
x = scatter.xScale();
5174
y = scatter.yScale();
5179
//------------------------------------------------------------
5182
//------------------------------------------------------------
5183
// Setup containers and skeleton of chart
5185
var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5186
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5187
var defsEnter = wrapEnter.append('defs');
5188
var gEnter = wrapEnter.append('g');
5189
var g = wrap.select('g')
5191
gEnter.append('g').attr('class', 'nv-groups');
5192
gEnter.append('g').attr('class', 'nv-scatterWrap');
5194
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5196
//------------------------------------------------------------
5202
.width(availableWidth)
5203
.height(availableHeight)
5205
var scatterWrap = wrap.select('.nv-scatterWrap');
5206
//.datum(data); // Data automatically trickles down from the wrap
5208
scatterWrap.transition().call(scatter);
5212
defsEnter.append('clipPath')
5213
.attr('id', 'nv-edge-clip-' + scatter.id())
5216
wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5217
.attr('width', availableWidth)
5218
.attr('height', (availableHeight > 0) ? availableHeight : 0);
5220
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5222
.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5227
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5228
.data(function(d) { return d }, function(d) { return d.key });
5229
groups.enter().append('g')
5230
.style('stroke-opacity', 1e-6)
5231
.style('fill-opacity', 1e-6);
5233
groups.exit().remove();
5236
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
5237
.classed('hover', function(d) { return d.hover })
5238
.style('fill', function(d,i){ return color(d, i) })
5239
.style('stroke', function(d,i){ return color(d, i)});
5242
.style('stroke-opacity', 1)
5243
.style('fill-opacity', .5);
5247
var areaPaths = groups.selectAll('path.nv-area')
5248
.data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5249
areaPaths.enter().append('path')
5250
.attr('class', 'nv-area')
5251
.attr('d', function(d) {
5252
return d3.svg.area()
5253
.interpolate(interpolate)
5255
.x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5256
.y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5257
.y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5258
//.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5259
.apply(this, [d.values])
5261
groups.exit().selectAll('path.nv-area')
5266
.attr('d', function(d) {
5267
return d3.svg.area()
5268
.interpolate(interpolate)
5270
.x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5271
.y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5272
.y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5273
//.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5274
.apply(this, [d.values])
5279
var linePaths = groups.selectAll('path.nv-line')
5280
.data(function(d) { return [d.values] });
5281
linePaths.enter().append('path')
5282
.attr('class', 'nv-line')
5285
.interpolate(interpolate)
5287
.x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5288
.y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5295
.interpolate(interpolate)
5297
.x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5298
.y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5303
//store old scales for use in transitions on update
5313
//============================================================
5314
// Expose Public Variables
5315
//------------------------------------------------------------
5317
chart.dispatch = scatter.dispatch;
5318
chart.scatter = scatter;
5320
d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
5321
'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights');
5323
chart.options = nv.utils.optionsFunc.bind(chart);
5325
chart.margin = function(_) {
5326
if (!arguments.length) return margin;
5327
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5328
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5329
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5330
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5334
chart.width = function(_) {
5335
if (!arguments.length) return width;
5340
chart.height = function(_) {
5341
if (!arguments.length) return height;
5346
chart.x = function(_) {
5347
if (!arguments.length) return getX;
5353
chart.y = function(_) {
5354
if (!arguments.length) return getY;
5360
chart.clipEdge = function(_) {
5361
if (!arguments.length) return clipEdge;
5366
chart.color = function(_) {
5367
if (!arguments.length) return color;
5368
color = nv.utils.getColor(_);
5369
scatter.color(color);
5373
chart.interpolate = function(_) {
5374
if (!arguments.length) return interpolate;
5379
chart.defined = function(_) {
5380
if (!arguments.length) return defined;
5385
chart.isArea = function(_) {
5386
if (!arguments.length) return isArea;
5387
isArea = d3.functor(_);
5391
//============================================================
5397
nv.models.lineChart = function() {
5399
//============================================================
5400
// Public Variables with Default Settings
5401
//------------------------------------------------------------
5403
var lines = nv.models.line()
5404
, xAxis = nv.models.axis()
5405
, yAxis = nv.models.axis()
5406
, legend = nv.models.legend()
5407
, interactiveLayer = nv.interactiveGuideline()
5410
var margin = {top: 30, right: 20, bottom: 50, left: 60}
5411
, color = nv.utils.defaultColor()
5417
, rightAlignYAxis = false
5418
, useInteractiveGuideline = false
5420
, tooltip = function(key, x, y, e, graph) {
5421
return '<h3>' + key + '</h3>' +
5422
'<p>' + y + ' at ' + x + '</p>'
5427
, defaultState = null
5428
, noData = 'No Data Available.'
5429
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5430
, transitionDuration = 250
5438
.orient((rightAlignYAxis) ? 'right' : 'left')
5441
//============================================================
5444
//============================================================
5445
// Private Variables
5446
//------------------------------------------------------------
5448
var showTooltip = function(e, offsetElement) {
5449
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5450
top = e.pos[1] + ( offsetElement.offsetTop || 0),
5451
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5452
y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5453
content = tooltip(e.series.key, x, y, e, chart);
5455
nv.tooltip.show([left, top], content, null, null, offsetElement);
5458
//============================================================
5461
function chart(selection) {
5462
selection.each(function(data) {
5463
var container = d3.select(this),
5466
var availableWidth = (width || parseInt(container.style('width')) || 960)
5467
- margin.left - margin.right,
5468
availableHeight = (height || parseInt(container.style('height')) || 400)
5469
- margin.top - margin.bottom;
5472
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5473
chart.container = this;
5475
//set state.disabled
5476
state.disabled = data.map(function(d) { return !!d.disabled });
5479
if (!defaultState) {
5482
for (key in state) {
5483
if (state[key] instanceof Array)
5484
defaultState[key] = state[key].slice(0);
5486
defaultState[key] = state[key];
5490
//------------------------------------------------------------
5491
// Display noData message if there's nothing to show.
5493
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5494
var noDataText = container.selectAll('.nv-noData').data([noData]);
5496
noDataText.enter().append('text')
5497
.attr('class', 'nvd3 nv-noData')
5498
.attr('dy', '-.7em')
5499
.style('text-anchor', 'middle');
5502
.attr('x', margin.left + availableWidth / 2)
5503
.attr('y', margin.top + availableHeight / 2)
5504
.text(function(d) { return d });
5508
container.selectAll('.nv-noData').remove();
5511
//------------------------------------------------------------
5514
//------------------------------------------------------------
5520
//------------------------------------------------------------
5523
//------------------------------------------------------------
5524
// Setup containers and skeleton of chart
5526
var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
5527
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
5528
var g = wrap.select('g');
5530
gEnter.append("rect").style("opacity",0);
5531
gEnter.append('g').attr('class', 'nv-x nv-axis');
5532
gEnter.append('g').attr('class', 'nv-y nv-axis');
5533
gEnter.append('g').attr('class', 'nv-linesWrap');
5534
gEnter.append('g').attr('class', 'nv-legendWrap');
5535
gEnter.append('g').attr('class', 'nv-interactive');
5538
.attr("width",availableWidth)
5539
.attr("height",(availableHeight > 0) ? availableHeight : 0);
5540
//------------------------------------------------------------
5544
legend.width(availableWidth);
5546
g.select('.nv-legendWrap')
5550
if ( margin.top != legend.height()) {
5551
margin.top = legend.height();
5552
availableHeight = (height || parseInt(container.style('height')) || 400)
5553
- margin.top - margin.bottom;
5556
wrap.select('.nv-legendWrap')
5557
.attr('transform', 'translate(0,' + (-margin.top) +')')
5560
//------------------------------------------------------------
5562
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5564
if (rightAlignYAxis) {
5565
g.select(".nv-y.nv-axis")
5566
.attr("transform", "translate(" + availableWidth + ",0)");
5569
//------------------------------------------------------------
5570
// Main Chart Component(s)
5573
//------------------------------------------------------------
5574
//Set up interactive layer
5575
if (useInteractiveGuideline) {
5577
.width(availableWidth)
5578
.height(availableHeight)
5579
.margin({left:margin.left, top:margin.top})
5580
.svgContainer(container)
5582
wrap.select(".nv-interactive").call(interactiveLayer);
5587
.width(availableWidth)
5588
.height(availableHeight)
5589
.color(data.map(function(d,i) {
5590
return d.color || color(d, i);
5591
}).filter(function(d,i) { return !data[i].disabled }));
5594
var linesWrap = g.select('.nv-linesWrap')
5595
.datum(data.filter(function(d) { return !d.disabled }))
5597
linesWrap.transition().call(lines);
5599
//------------------------------------------------------------
5602
//------------------------------------------------------------
5608
.ticks( availableWidth / 100 )
5609
.tickSize(-availableHeight, 0);
5611
g.select('.nv-x.nv-axis')
5612
.attr('transform', 'translate(0,' + y.range()[0] + ')');
5613
g.select('.nv-x.nv-axis')
5621
.ticks( availableHeight / 36 )
5622
.tickSize( -availableWidth, 0);
5624
g.select('.nv-y.nv-axis')
5628
//------------------------------------------------------------
5631
//============================================================
5632
// Event Handling/Dispatching (in chart's scope)
5633
//------------------------------------------------------------
5635
legend.dispatch.on('stateChange', function(newState) {
5637
dispatch.stateChange(state);
5641
interactiveLayer.dispatch.on('elementMousemove', function(e) {
5642
lines.clearHighlights();
5643
var singlePoint, pointIndex, pointXLocation, allData = [];
5645
.filter(function(series, i) {
5646
series.seriesIndex = i;
5647
return !series.disabled;
5649
.forEach(function(series,i) {
5650
pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5651
lines.highlightPoint(i, pointIndex, true);
5652
var point = series.values[pointIndex];
5653
if (typeof point === 'undefined') return;
5654
if (typeof singlePoint === 'undefined') singlePoint = point;
5655
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5658
value: chart.y()(point, pointIndex),
5659
color: color(series,series.seriesIndex)
5662
//Highlight the tooltip entry based on which point the mouse is closest to.
5663
if (allData.length > 2) {
5664
var yValue = chart.yScale().invert(e.mouseY);
5665
var domainExtent = Math.abs(chart.yScale().domain()[0] - chart.yScale().domain()[1]);
5666
var threshold = 0.03 * domainExtent;
5667
var indexToHighlight = nv.nearestValueIndex(allData.map(function(d){return d.value}),yValue,threshold);
5668
if (indexToHighlight !== null)
5669
allData[indexToHighlight].highlight = true;
5672
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5673
interactiveLayer.tooltip
5674
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5675
.chartContainer(that.parentNode)
5677
.valueFormatter(function(d,i) {
5678
return yAxis.tickFormat()(d);
5687
interactiveLayer.renderGuideLine(pointXLocation);
5691
interactiveLayer.dispatch.on("elementMouseout",function(e) {
5692
dispatch.tooltipHide();
5693
lines.clearHighlights();
5696
dispatch.on('tooltipShow', function(e) {
5697
if (tooltips) showTooltip(e, that.parentNode);
5701
dispatch.on('changeState', function(e) {
5703
if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
5704
data.forEach(function(series,i) {
5705
series.disabled = e.disabled[i];
5708
state.disabled = e.disabled;
5714
//============================================================
5722
//============================================================
5723
// Event Handling/Dispatching (out of chart's scope)
5724
//------------------------------------------------------------
5726
lines.dispatch.on('elementMouseover.tooltip', function(e) {
5727
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5728
dispatch.tooltipShow(e);
5731
lines.dispatch.on('elementMouseout.tooltip', function(e) {
5732
dispatch.tooltipHide(e);
5735
dispatch.on('tooltipHide', function() {
5736
if (tooltips) nv.tooltip.cleanup();
5739
//============================================================
5742
//============================================================
5743
// Expose Public Variables
5744
//------------------------------------------------------------
5746
// expose chart's sub-components
5747
chart.dispatch = dispatch;
5748
chart.lines = lines;
5749
chart.legend = legend;
5750
chart.xAxis = xAxis;
5751
chart.yAxis = yAxis;
5752
chart.interactiveLayer = interactiveLayer;
5754
d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange'
5755
, 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
5757
chart.options = nv.utils.optionsFunc.bind(chart);
5759
chart.margin = function(_) {
5760
if (!arguments.length) return margin;
5761
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5762
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5763
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5764
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5768
chart.width = function(_) {
5769
if (!arguments.length) return width;
5774
chart.height = function(_) {
5775
if (!arguments.length) return height;
5780
chart.color = function(_) {
5781
if (!arguments.length) return color;
5782
color = nv.utils.getColor(_);
5783
legend.color(color);
5787
chart.showLegend = function(_) {
5788
if (!arguments.length) return showLegend;
5793
chart.showXAxis = function(_) {
5794
if (!arguments.length) return showXAxis;
5799
chart.showYAxis = function(_) {
5800
if (!arguments.length) return showYAxis;
5805
chart.rightAlignYAxis = function(_) {
5806
if(!arguments.length) return rightAlignYAxis;
5807
rightAlignYAxis = _;
5808
yAxis.orient( (_) ? 'right' : 'left');
5812
chart.useInteractiveGuideline = function(_) {
5813
if(!arguments.length) return useInteractiveGuideline;
5814
useInteractiveGuideline = _;
5816
chart.interactive(false);
5817
chart.useVoronoi(false);
5822
chart.tooltips = function(_) {
5823
if (!arguments.length) return tooltips;
5828
chart.tooltipContent = function(_) {
5829
if (!arguments.length) return tooltip;
5834
chart.state = function(_) {
5835
if (!arguments.length) return state;
5840
chart.defaultState = function(_) {
5841
if (!arguments.length) return defaultState;
5846
chart.noData = function(_) {
5847
if (!arguments.length) return noData;
5852
chart.transitionDuration = function(_) {
5853
if (!arguments.length) return transitionDuration;
5854
transitionDuration = _;
5858
//============================================================
5864
nv.models.linePlusBarChart = function() {
5866
//============================================================
5867
// Public Variables with Default Settings
5868
//------------------------------------------------------------
5870
var lines = nv.models.line()
5871
, bars = nv.models.historicalBar()
5872
, xAxis = nv.models.axis()
5873
, y1Axis = nv.models.axis()
5874
, y2Axis = nv.models.axis()
5875
, legend = nv.models.legend()
5878
var margin = {top: 30, right: 60, bottom: 50, left: 60}
5881
, getX = function(d) { return d.x }
5882
, getY = function(d) { return d.y }
5883
, color = nv.utils.defaultColor()
5886
, tooltip = function(key, x, y, e, graph) {
5887
return '<h3>' + key + '</h3>' +
5888
'<p>' + y + ' at ' + x + '</p>';
5894
, defaultState = null
5895
, noData = "No Data Available."
5896
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5909
.highlightZero(false)
5918
//============================================================
5921
//============================================================
5922
// Private Variables
5923
//------------------------------------------------------------
5925
var showTooltip = function(e, offsetElement) {
5926
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5927
top = e.pos[1] + ( offsetElement.offsetTop || 0),
5928
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5929
y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5930
content = tooltip(e.series.key, x, y, e, chart);
5932
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5936
//------------------------------------------------------------
5940
function chart(selection) {
5941
selection.each(function(data) {
5942
var container = d3.select(this),
5945
var availableWidth = (width || parseInt(container.style('width')) || 960)
5946
- margin.left - margin.right,
5947
availableHeight = (height || parseInt(container.style('height')) || 400)
5948
- margin.top - margin.bottom;
5950
chart.update = function() { container.transition().call(chart); };
5951
// chart.container = this;
5953
//set state.disabled
5954
state.disabled = data.map(function(d) { return !!d.disabled });
5956
if (!defaultState) {
5959
for (key in state) {
5960
if (state[key] instanceof Array)
5961
defaultState[key] = state[key].slice(0);
5963
defaultState[key] = state[key];
5967
//------------------------------------------------------------
5968
// Display No Data message if there's nothing to show.
5970
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5971
var noDataText = container.selectAll('.nv-noData').data([noData]);
5973
noDataText.enter().append('text')
5974
.attr('class', 'nvd3 nv-noData')
5975
.attr('dy', '-.7em')
5976
.style('text-anchor', 'middle');
5979
.attr('x', margin.left + availableWidth / 2)
5980
.attr('y', margin.top + availableHeight / 2)
5981
.text(function(d) { return d });
5985
container.selectAll('.nv-noData').remove();
5988
//------------------------------------------------------------
5991
//------------------------------------------------------------
5994
var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5995
var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5997
//x = xAxis.scale();
5998
x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
5999
//x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
6001
y2 = lines.yScale();
6003
//------------------------------------------------------------
6005
//------------------------------------------------------------
6006
// Setup containers and skeleton of chart
6008
var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6009
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6010
var g = wrap.select('g');
6012
gEnter.append('g').attr('class', 'nv-x nv-axis');
6013
gEnter.append('g').attr('class', 'nv-y1 nv-axis');
6014
gEnter.append('g').attr('class', 'nv-y2 nv-axis');
6015
gEnter.append('g').attr('class', 'nv-barsWrap');
6016
gEnter.append('g').attr('class', 'nv-linesWrap');
6017
gEnter.append('g').attr('class', 'nv-legendWrap');
6019
//------------------------------------------------------------
6022
//------------------------------------------------------------
6026
legend.width( availableWidth / 2 );
6028
g.select('.nv-legendWrap')
6029
.datum(data.map(function(series) {
6030
series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6031
series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6036
if ( margin.top != legend.height()) {
6037
margin.top = legend.height();
6038
availableHeight = (height || parseInt(container.style('height')) || 400)
6039
- margin.top - margin.bottom;
6042
g.select('.nv-legendWrap')
6043
.attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6046
//------------------------------------------------------------
6049
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6052
//------------------------------------------------------------
6053
// Main Chart Component(s)
6057
.width(availableWidth)
6058
.height(availableHeight)
6059
.color(data.map(function(d,i) {
6060
return d.color || color(d, i);
6061
}).filter(function(d,i) { return !data[i].disabled && !data[i].bar }))
6064
.width(availableWidth)
6065
.height(availableHeight)
6066
.color(data.map(function(d,i) {
6067
return d.color || color(d, i);
6068
}).filter(function(d,i) { return !data[i].disabled && data[i].bar }))
6072
var barsWrap = g.select('.nv-barsWrap')
6073
.datum(dataBars.length ? dataBars : [{values:[]}])
6075
var linesWrap = g.select('.nv-linesWrap')
6076
.datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
6077
//.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
6079
d3.transition(barsWrap).call(bars);
6080
d3.transition(linesWrap).call(lines);
6082
//------------------------------------------------------------
6085
//------------------------------------------------------------
6090
.ticks( availableWidth / 100 )
6091
.tickSize(-availableHeight, 0);
6093
g.select('.nv-x.nv-axis')
6094
.attr('transform', 'translate(0,' + y1.range()[0] + ')');
6095
d3.transition(g.select('.nv-x.nv-axis'))
6101
.ticks( availableHeight / 36 )
6102
.tickSize(-availableWidth, 0);
6104
d3.transition(g.select('.nv-y1.nv-axis'))
6105
.style('opacity', dataBars.length ? 1 : 0)
6111
.ticks( availableHeight / 36 )
6112
.tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6114
g.select('.nv-y2.nv-axis')
6115
.style('opacity', dataLines.length ? 1 : 0)
6116
.attr('transform', 'translate(' + availableWidth + ',0)');
6117
//.attr('transform', 'translate(' + x.range()[1] + ',0)');
6119
d3.transition(g.select('.nv-y2.nv-axis'))
6122
//------------------------------------------------------------
6125
//============================================================
6126
// Event Handling/Dispatching (in chart's scope)
6127
//------------------------------------------------------------
6129
legend.dispatch.on('stateChange', function(newState) {
6131
dispatch.stateChange(state);
6135
dispatch.on('tooltipShow', function(e) {
6136
if (tooltips) showTooltip(e, that.parentNode);
6140
// Update chart from a state object passed to event handler
6141
dispatch.on('changeState', function(e) {
6143
if (typeof e.disabled !== 'undefined') {
6144
data.forEach(function(series,i) {
6145
series.disabled = e.disabled[i];
6148
state.disabled = e.disabled;
6154
//============================================================
6163
//============================================================
6164
// Event Handling/Dispatching (out of chart's scope)
6165
//------------------------------------------------------------
6167
lines.dispatch.on('elementMouseover.tooltip', function(e) {
6168
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6169
dispatch.tooltipShow(e);
6172
lines.dispatch.on('elementMouseout.tooltip', function(e) {
6173
dispatch.tooltipHide(e);
6176
bars.dispatch.on('elementMouseover.tooltip', function(e) {
6177
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6178
dispatch.tooltipShow(e);
6181
bars.dispatch.on('elementMouseout.tooltip', function(e) {
6182
dispatch.tooltipHide(e);
6185
dispatch.on('tooltipHide', function() {
6186
if (tooltips) nv.tooltip.cleanup();
6189
//============================================================
6192
//============================================================
6193
// Expose Public Variables
6194
//------------------------------------------------------------
6196
// expose chart's sub-components
6197
chart.dispatch = dispatch;
6198
chart.legend = legend;
6199
chart.lines = lines;
6201
chart.xAxis = xAxis;
6202
chart.y1Axis = y1Axis;
6203
chart.y2Axis = y2Axis;
6205
d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6206
//TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6207
//d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6209
chart.options = nv.utils.optionsFunc.bind(chart);
6211
chart.x = function(_) {
6212
if (!arguments.length) return getX;
6219
chart.y = function(_) {
6220
if (!arguments.length) return getY;
6227
chart.margin = function(_) {
6228
if (!arguments.length) return margin;
6229
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6230
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6231
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6232
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6236
chart.width = function(_) {
6237
if (!arguments.length) return width;
6242
chart.height = function(_) {
6243
if (!arguments.length) return height;
6248
chart.color = function(_) {
6249
if (!arguments.length) return color;
6250
color = nv.utils.getColor(_);
6251
legend.color(color);
6255
chart.showLegend = function(_) {
6256
if (!arguments.length) return showLegend;
6261
chart.tooltips = function(_) {
6262
if (!arguments.length) return tooltips;
6267
chart.tooltipContent = function(_) {
6268
if (!arguments.length) return tooltip;
6273
chart.state = function(_) {
6274
if (!arguments.length) return state;
6279
chart.defaultState = function(_) {
6280
if (!arguments.length) return defaultState;
6285
chart.noData = function(_) {
6286
if (!arguments.length) return noData;
6291
//============================================================
6296
nv.models.lineWithFocusChart = function() {
6298
//============================================================
6299
// Public Variables with Default Settings
6300
//------------------------------------------------------------
6302
var lines = nv.models.line()
6303
, lines2 = nv.models.line()
6304
, xAxis = nv.models.axis()
6305
, yAxis = nv.models.axis()
6306
, x2Axis = nv.models.axis()
6307
, y2Axis = nv.models.axis()
6308
, legend = nv.models.legend()
6309
, brush = d3.svg.brush()
6312
var margin = {top: 30, right: 30, bottom: 30, left: 60}
6313
, margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6314
, color = nv.utils.defaultColor()
6323
, brushExtent = null
6325
, tooltip = function(key, x, y, e, graph) {
6326
return '<h3>' + key + '</h3>' +
6327
'<p>' + y + ' at ' + x + '</p>'
6329
, noData = "No Data Available."
6330
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6331
, transitionDuration = 250
6354
//============================================================
6357
//============================================================
6358
// Private Variables
6359
//------------------------------------------------------------
6361
var showTooltip = function(e, offsetElement) {
6362
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6363
top = e.pos[1] + ( offsetElement.offsetTop || 0),
6364
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6365
y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
6366
content = tooltip(e.series.key, x, y, e, chart);
6368
nv.tooltip.show([left, top], content, null, null, offsetElement);
6371
//============================================================
6374
function chart(selection) {
6375
selection.each(function(data) {
6376
var container = d3.select(this),
6379
var availableWidth = (width || parseInt(container.style('width')) || 960)
6380
- margin.left - margin.right,
6381
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6382
- margin.top - margin.bottom - height2,
6383
availableHeight2 = height2 - margin2.top - margin2.bottom;
6385
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
6386
chart.container = this;
6389
//------------------------------------------------------------
6390
// Display No Data message if there's nothing to show.
6392
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6393
var noDataText = container.selectAll('.nv-noData').data([noData]);
6395
noDataText.enter().append('text')
6396
.attr('class', 'nvd3 nv-noData')
6397
.attr('dy', '-.7em')
6398
.style('text-anchor', 'middle');
6401
.attr('x', margin.left + availableWidth / 2)
6402
.attr('y', margin.top + availableHeight1 / 2)
6403
.text(function(d) { return d });
6407
container.selectAll('.nv-noData').remove();
6410
//------------------------------------------------------------
6413
//------------------------------------------------------------
6418
x2 = lines2.xScale();
6419
y2 = lines2.yScale();
6421
//------------------------------------------------------------
6424
//------------------------------------------------------------
6425
// Setup containers and skeleton of chart
6427
var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
6428
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
6429
var g = wrap.select('g');
6431
gEnter.append('g').attr('class', 'nv-legendWrap');
6433
var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6434
focusEnter.append('g').attr('class', 'nv-x nv-axis');
6435
focusEnter.append('g').attr('class', 'nv-y nv-axis');
6436
focusEnter.append('g').attr('class', 'nv-linesWrap');
6438
var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6439
contextEnter.append('g').attr('class', 'nv-x nv-axis');
6440
contextEnter.append('g').attr('class', 'nv-y nv-axis');
6441
contextEnter.append('g').attr('class', 'nv-linesWrap');
6442
contextEnter.append('g').attr('class', 'nv-brushBackground');
6443
contextEnter.append('g').attr('class', 'nv-x nv-brush');
6445
//------------------------------------------------------------
6448
//------------------------------------------------------------
6452
legend.width(availableWidth);
6454
g.select('.nv-legendWrap')
6458
if ( margin.top != legend.height()) {
6459
margin.top = legend.height();
6460
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6461
- margin.top - margin.bottom - height2;
6464
g.select('.nv-legendWrap')
6465
.attr('transform', 'translate(0,' + (-margin.top) +')')
6468
//------------------------------------------------------------
6471
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6474
//------------------------------------------------------------
6475
// Main Chart Component(s)
6478
.width(availableWidth)
6479
.height(availableHeight1)
6482
.map(function(d,i) {
6483
return d.color || color(d, i);
6485
.filter(function(d,i) {
6486
return !data[i].disabled;
6491
.defined(lines.defined())
6492
.width(availableWidth)
6493
.height(availableHeight2)
6496
.map(function(d,i) {
6497
return d.color || color(d, i);
6499
.filter(function(d,i) {
6500
return !data[i].disabled;
6504
g.select('.nv-context')
6505
.attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6507
var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
6508
.datum(data.filter(function(d) { return !d.disabled }))
6510
d3.transition(contextLinesWrap).call(lines2);
6512
//------------------------------------------------------------
6516
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6517
.datum(data.filter(function(d) { return !d.disabled }))
6519
d3.transition(focusLinesWrap).call(lines);
6523
//------------------------------------------------------------
6524
// Setup Main (Focus) Axes
6528
.ticks( availableWidth / 100 )
6529
.tickSize(-availableHeight1, 0);
6533
.ticks( availableHeight1 / 36 )
6534
.tickSize( -availableWidth, 0);
6536
g.select('.nv-focus .nv-x.nv-axis')
6537
.attr('transform', 'translate(0,' + availableHeight1 + ')');
6539
//------------------------------------------------------------
6542
//------------------------------------------------------------
6547
.on('brush', function() {
6548
//When brushing, turn off transitions because chart needs to change immediately.
6549
var oldTransition = chart.transitionDuration();
6550
chart.transitionDuration(0);
6552
chart.transitionDuration(oldTransition);
6555
if (brushExtent) brush.extent(brushExtent);
6557
var brushBG = g.select('.nv-brushBackground').selectAll('g')
6558
.data([brushExtent || brush.extent()])
6560
var brushBGenter = brushBG.enter()
6563
brushBGenter.append('rect')
6564
.attr('class', 'left')
6567
.attr('height', availableHeight2);
6569
brushBGenter.append('rect')
6570
.attr('class', 'right')
6573
.attr('height', availableHeight2);
6575
var gBrush = g.select('.nv-x.nv-brush')
6577
gBrush.selectAll('rect')
6579
.attr('height', availableHeight2);
6580
gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6584
//------------------------------------------------------------
6587
//------------------------------------------------------------
6588
// Setup Secondary (Context) Axes
6592
.ticks( availableWidth / 100 )
6593
.tickSize(-availableHeight2, 0);
6595
g.select('.nv-context .nv-x.nv-axis')
6596
.attr('transform', 'translate(0,' + y2.range()[0] + ')');
6597
d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6603
.ticks( availableHeight2 / 36 )
6604
.tickSize( -availableWidth, 0);
6606
d3.transition(g.select('.nv-context .nv-y.nv-axis'))
6609
g.select('.nv-context .nv-x.nv-axis')
6610
.attr('transform', 'translate(0,' + y2.range()[0] + ')');
6612
//------------------------------------------------------------
6615
//============================================================
6616
// Event Handling/Dispatching (in chart's scope)
6617
//------------------------------------------------------------
6619
legend.dispatch.on('stateChange', function(newState) {
6623
dispatch.on('tooltipShow', function(e) {
6624
if (tooltips) showTooltip(e, that.parentNode);
6627
//============================================================
6630
//============================================================
6632
//------------------------------------------------------------
6634
// Taken from crossfilter (http://square.github.com/crossfilter/)
6635
function resizePath(d) {
6636
var e = +(d == 'e'),
6638
y = availableHeight2 / 3;
6639
return 'M' + (.5 * x) + ',' + y
6640
+ 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6642
+ 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6644
+ 'M' + (2.5 * x) + ',' + (y + 8)
6646
+ 'M' + (4.5 * x) + ',' + (y + 8)
6647
+ 'V' + (2 * y - 8);
6651
function updateBrushBG() {
6652
if (!brush.empty()) brush.extent(brushExtent);
6654
.data([brush.empty() ? x2.domain() : brushExtent])
6655
.each(function(d,i) {
6656
var leftWidth = x2(d[0]) - x.range()[0],
6657
rightWidth = x.range()[1] - x2(d[1]);
6658
d3.select(this).select('.left')
6659
.attr('width', leftWidth < 0 ? 0 : leftWidth);
6661
d3.select(this).select('.right')
6662
.attr('x', x2(d[1]))
6663
.attr('width', rightWidth < 0 ? 0 : rightWidth);
6668
function onBrush() {
6669
brushExtent = brush.empty() ? null : brush.extent();
6670
var extent = brush.empty() ? x2.domain() : brush.extent();
6672
//The brush extent cannot be less than one. If it is, don't update the line chart.
6673
if (Math.abs(extent[0] - extent[1]) <= 1) {
6677
dispatch.brush({extent: extent, brush: brush});
6682
// Update Main (Focus)
6683
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6686
.filter(function(d) { return !d.disabled })
6687
.map(function(d,i) {
6690
values: d.values.filter(function(d,i) {
6691
return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6696
focusLinesWrap.transition().duration(transitionDuration).call(lines);
6699
// Update Main (Focus) Axes
6700
g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
6702
g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
6706
//============================================================
6715
//============================================================
6716
// Event Handling/Dispatching (out of chart's scope)
6717
//------------------------------------------------------------
6719
lines.dispatch.on('elementMouseover.tooltip', function(e) {
6720
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6721
dispatch.tooltipShow(e);
6724
lines.dispatch.on('elementMouseout.tooltip', function(e) {
6725
dispatch.tooltipHide(e);
6728
dispatch.on('tooltipHide', function() {
6729
if (tooltips) nv.tooltip.cleanup();
6732
//============================================================
6735
//============================================================
6736
// Expose Public Variables
6737
//------------------------------------------------------------
6739
// expose chart's sub-components
6740
chart.dispatch = dispatch;
6741
chart.legend = legend;
6742
chart.lines = lines;
6743
chart.lines2 = lines2;
6744
chart.xAxis = xAxis;
6745
chart.yAxis = yAxis;
6746
chart.x2Axis = x2Axis;
6747
chart.y2Axis = y2Axis;
6749
d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6751
chart.options = nv.utils.optionsFunc.bind(chart);
6753
chart.x = function(_) {
6754
if (!arguments.length) return lines.x;
6760
chart.y = function(_) {
6761
if (!arguments.length) return lines.y;
6767
chart.margin = function(_) {
6768
if (!arguments.length) return margin;
6769
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6770
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6771
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6772
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6776
chart.margin2 = function(_) {
6777
if (!arguments.length) return margin2;
6782
chart.width = function(_) {
6783
if (!arguments.length) return width;
6788
chart.height = function(_) {
6789
if (!arguments.length) return height;
6794
chart.height2 = function(_) {
6795
if (!arguments.length) return height2;
6800
chart.color = function(_) {
6801
if (!arguments.length) return color;
6802
color =nv.utils.getColor(_);
6803
legend.color(color);
6807
chart.showLegend = function(_) {
6808
if (!arguments.length) return showLegend;
6813
chart.tooltips = function(_) {
6814
if (!arguments.length) return tooltips;
6819
chart.tooltipContent = function(_) {
6820
if (!arguments.length) return tooltip;
6825
chart.interpolate = function(_) {
6826
if (!arguments.length) return lines.interpolate();
6827
lines.interpolate(_);
6828
lines2.interpolate(_);
6832
chart.noData = function(_) {
6833
if (!arguments.length) return noData;
6838
// Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
6839
chart.xTickFormat = function(_) {
6840
if (!arguments.length) return xAxis.tickFormat();
6841
xAxis.tickFormat(_);
6842
x2Axis.tickFormat(_);
6846
chart.yTickFormat = function(_) {
6847
if (!arguments.length) return yAxis.tickFormat();
6848
yAxis.tickFormat(_);
6849
y2Axis.tickFormat(_);
6853
chart.brushExtent = function(_) {
6854
if (!arguments.length) return brushExtent;
6859
chart.transitionDuration = function(_) {
6860
if (!arguments.length) return transitionDuration;
6861
transitionDuration = _;
6865
//============================================================
6871
nv.models.linePlusBarWithFocusChart = function() {
6873
//============================================================
6874
// Public Variables with Default Settings
6875
//------------------------------------------------------------
6877
var lines = nv.models.line()
6878
, lines2 = nv.models.line()
6879
, bars = nv.models.historicalBar()
6880
, bars2 = nv.models.historicalBar()
6881
, xAxis = nv.models.axis()
6882
, x2Axis = nv.models.axis()
6883
, y1Axis = nv.models.axis()
6884
, y2Axis = nv.models.axis()
6885
, y3Axis = nv.models.axis()
6886
, y4Axis = nv.models.axis()
6887
, legend = nv.models.legend()
6888
, brush = d3.svg.brush()
6891
var margin = {top: 30, right: 30, bottom: 30, left: 60}
6892
, margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6896
, getX = function(d) { return d.x }
6897
, getY = function(d) { return d.y }
6898
, color = nv.utils.defaultColor()
6901
, brushExtent = null
6903
, tooltip = function(key, x, y, e, graph) {
6904
return '<h3>' + key + '</h3>' +
6905
'<p>' + y + ' at ' + x + '</p>';
6913
, noData = "No Data Available."
6914
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6915
, transitionDuration = 0
6945
//============================================================
6948
//============================================================
6949
// Private Variables
6950
//------------------------------------------------------------
6952
var showTooltip = function(e, offsetElement) {
6954
e.pointIndex += Math.ceil(extent[0]);
6956
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6957
top = e.pos[1] + ( offsetElement.offsetTop || 0),
6958
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6959
y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6960
content = tooltip(e.series.key, x, y, e, chart);
6962
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6965
//------------------------------------------------------------
6969
function chart(selection) {
6970
selection.each(function(data) {
6971
var container = d3.select(this),
6974
var availableWidth = (width || parseInt(container.style('width')) || 960)
6975
- margin.left - margin.right,
6976
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6977
- margin.top - margin.bottom - height2,
6978
availableHeight2 = height2 - margin2.top - margin2.bottom;
6980
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6981
chart.container = this;
6984
//------------------------------------------------------------
6985
// Display No Data message if there's nothing to show.
6987
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6988
var noDataText = container.selectAll('.nv-noData').data([noData]);
6990
noDataText.enter().append('text')
6991
.attr('class', 'nvd3 nv-noData')
6992
.attr('dy', '-.7em')
6993
.style('text-anchor', 'middle');
6996
.attr('x', margin.left + availableWidth / 2)
6997
.attr('y', margin.top + availableHeight1 / 2)
6998
.text(function(d) { return d });
7002
container.selectAll('.nv-noData').remove();
7005
//------------------------------------------------------------
7008
//------------------------------------------------------------
7011
var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
7012
var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
7015
x2 = x2Axis.scale();
7017
y2 = lines.yScale();
7018
y3 = bars2.yScale();
7019
y4 = lines2.yScale();
7022
.filter(function(d) { return !d.disabled && d.bar })
7024
return d.values.map(function(d,i) {
7025
return { x: getX(d,i), y: getY(d,i) }
7030
.filter(function(d) { return !d.disabled && !d.bar })
7032
return d.values.map(function(d,i) {
7033
return { x: getX(d,i), y: getY(d,i) }
7037
x .range([0, availableWidth]);
7039
x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
7040
.range([0, availableWidth]);
7043
//------------------------------------------------------------
7046
//------------------------------------------------------------
7047
// Setup containers and skeleton of chart
7049
var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
7050
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
7051
var g = wrap.select('g');
7053
gEnter.append('g').attr('class', 'nv-legendWrap');
7055
var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
7056
focusEnter.append('g').attr('class', 'nv-x nv-axis');
7057
focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
7058
focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
7059
focusEnter.append('g').attr('class', 'nv-barsWrap');
7060
focusEnter.append('g').attr('class', 'nv-linesWrap');
7062
var contextEnter = gEnter.append('g').attr('class', 'nv-context');
7063
contextEnter.append('g').attr('class', 'nv-x nv-axis');
7064
contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
7065
contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
7066
contextEnter.append('g').attr('class', 'nv-barsWrap');
7067
contextEnter.append('g').attr('class', 'nv-linesWrap');
7068
contextEnter.append('g').attr('class', 'nv-brushBackground');
7069
contextEnter.append('g').attr('class', 'nv-x nv-brush');
7072
//------------------------------------------------------------
7075
//------------------------------------------------------------
7079
legend.width( availableWidth / 2 );
7081
g.select('.nv-legendWrap')
7082
.datum(data.map(function(series) {
7083
series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
7084
series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
7089
if ( margin.top != legend.height()) {
7090
margin.top = legend.height();
7091
availableHeight1 = (height || parseInt(container.style('height')) || 400)
7092
- margin.top - margin.bottom - height2;
7095
g.select('.nv-legendWrap')
7096
.attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
7099
//------------------------------------------------------------
7102
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7105
//------------------------------------------------------------
7106
// Context Components
7109
.width(availableWidth)
7110
.height(availableHeight2)
7111
.color(data.map(function(d,i) {
7112
return d.color || color(d, i);
7113
}).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7116
.width(availableWidth)
7117
.height(availableHeight2)
7118
.color(data.map(function(d,i) {
7119
return d.color || color(d, i);
7120
}).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7122
var bars2Wrap = g.select('.nv-context .nv-barsWrap')
7123
.datum(dataBars.length ? dataBars : [{values:[]}]);
7125
var lines2Wrap = g.select('.nv-context .nv-linesWrap')
7126
.datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
7128
g.select('.nv-context')
7129
.attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7131
bars2Wrap.transition().call(bars2);
7132
lines2Wrap.transition().call(lines2);
7134
//------------------------------------------------------------
7138
//------------------------------------------------------------
7143
.on('brush', onBrush);
7145
if (brushExtent) brush.extent(brushExtent);
7147
var brushBG = g.select('.nv-brushBackground').selectAll('g')
7148
.data([brushExtent || brush.extent()])
7150
var brushBGenter = brushBG.enter()
7153
brushBGenter.append('rect')
7154
.attr('class', 'left')
7157
.attr('height', availableHeight2);
7159
brushBGenter.append('rect')
7160
.attr('class', 'right')
7163
.attr('height', availableHeight2);
7165
var gBrush = g.select('.nv-x.nv-brush')
7167
gBrush.selectAll('rect')
7169
.attr('height', availableHeight2);
7170
gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7172
//------------------------------------------------------------
7174
//------------------------------------------------------------
7175
// Setup Secondary (Context) Axes
7178
.ticks( availableWidth / 100 )
7179
.tickSize(-availableHeight2, 0);
7181
g.select('.nv-context .nv-x.nv-axis')
7182
.attr('transform', 'translate(0,' + y3.range()[0] + ')');
7183
g.select('.nv-context .nv-x.nv-axis').transition()
7189
.ticks( availableHeight2 / 36 )
7190
.tickSize( -availableWidth, 0);
7192
g.select('.nv-context .nv-y1.nv-axis')
7193
.style('opacity', dataBars.length ? 1 : 0)
7194
.attr('transform', 'translate(0,' + x2.range()[0] + ')');
7196
g.select('.nv-context .nv-y1.nv-axis').transition()
7202
.ticks( availableHeight2 / 36 )
7203
.tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7205
g.select('.nv-context .nv-y2.nv-axis')
7206
.style('opacity', dataLines.length ? 1 : 0)
7207
.attr('transform', 'translate(' + x2.range()[1] + ',0)');
7209
g.select('.nv-context .nv-y2.nv-axis').transition()
7212
//------------------------------------------------------------
7214
//============================================================
7215
// Event Handling/Dispatching (in chart's scope)
7216
//------------------------------------------------------------
7218
legend.dispatch.on('stateChange', function(newState) {
7222
dispatch.on('tooltipShow', function(e) {
7223
if (tooltips) showTooltip(e, that.parentNode);
7226
//============================================================
7229
//============================================================
7231
//------------------------------------------------------------
7233
// Taken from crossfilter (http://square.github.com/crossfilter/)
7234
function resizePath(d) {
7235
var e = +(d == 'e'),
7237
y = availableHeight2 / 3;
7238
return 'M' + (.5 * x) + ',' + y
7239
+ 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7241
+ 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7243
+ 'M' + (2.5 * x) + ',' + (y + 8)
7245
+ 'M' + (4.5 * x) + ',' + (y + 8)
7246
+ 'V' + (2 * y - 8);
7250
function updateBrushBG() {
7251
if (!brush.empty()) brush.extent(brushExtent);
7253
.data([brush.empty() ? x2.domain() : brushExtent])
7254
.each(function(d,i) {
7255
var leftWidth = x2(d[0]) - x2.range()[0],
7256
rightWidth = x2.range()[1] - x2(d[1]);
7257
d3.select(this).select('.left')
7258
.attr('width', leftWidth < 0 ? 0 : leftWidth);
7260
d3.select(this).select('.right')
7261
.attr('x', x2(d[1]))
7262
.attr('width', rightWidth < 0 ? 0 : rightWidth);
7267
function onBrush() {
7268
brushExtent = brush.empty() ? null : brush.extent();
7269
extent = brush.empty() ? x2.domain() : brush.extent();
7272
dispatch.brush({extent: extent, brush: brush});
7277
//------------------------------------------------------------
7278
// Prepare Main (Focus) Bars and Lines
7281
.width(availableWidth)
7282
.height(availableHeight1)
7283
.color(data.map(function(d,i) {
7284
return d.color || color(d, i);
7285
}).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7289
.width(availableWidth)
7290
.height(availableHeight1)
7291
.color(data.map(function(d,i) {
7292
return d.color || color(d, i);
7293
}).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7295
var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
7296
.datum(!dataBars.length ? [{values:[]}] :
7298
.map(function(d,i) {
7301
values: d.values.filter(function(d,i) {
7302
return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
7308
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7309
.datum(dataLines[0].disabled ? [{values:[]}] :
7311
.map(function(d,i) {
7314
values: d.values.filter(function(d,i) {
7315
return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7321
//------------------------------------------------------------
7324
//------------------------------------------------------------
7325
// Update Main (Focus) X Axis
7327
if (dataBars.length) {
7335
.ticks( availableWidth / 100 )
7336
.tickSize(-availableHeight1, 0);
7338
xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
7340
g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
7342
//------------------------------------------------------------
7345
//------------------------------------------------------------
7346
// Update Main (Focus) Bars and Lines
7348
focusBarsWrap.transition().duration(transitionDuration).call(bars);
7349
focusLinesWrap.transition().duration(transitionDuration).call(lines);
7351
//------------------------------------------------------------
7354
//------------------------------------------------------------
7355
// Setup and Update Main (Focus) Y Axes
7357
g.select('.nv-focus .nv-x.nv-axis')
7358
.attr('transform', 'translate(0,' + y1.range()[0] + ')');
7363
.ticks( availableHeight1 / 36 )
7364
.tickSize(-availableWidth, 0);
7366
g.select('.nv-focus .nv-y1.nv-axis')
7367
.style('opacity', dataBars.length ? 1 : 0);
7372
.ticks( availableHeight1 / 36 )
7373
.tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7375
g.select('.nv-focus .nv-y2.nv-axis')
7376
.style('opacity', dataLines.length ? 1 : 0)
7377
.attr('transform', 'translate(' + x.range()[1] + ',0)');
7379
g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
7381
g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
7385
//============================================================
7395
//============================================================
7396
// Event Handling/Dispatching (out of chart's scope)
7397
//------------------------------------------------------------
7399
lines.dispatch.on('elementMouseover.tooltip', function(e) {
7400
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7401
dispatch.tooltipShow(e);
7404
lines.dispatch.on('elementMouseout.tooltip', function(e) {
7405
dispatch.tooltipHide(e);
7408
bars.dispatch.on('elementMouseover.tooltip', function(e) {
7409
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7410
dispatch.tooltipShow(e);
7413
bars.dispatch.on('elementMouseout.tooltip', function(e) {
7414
dispatch.tooltipHide(e);
7417
dispatch.on('tooltipHide', function() {
7418
if (tooltips) nv.tooltip.cleanup();
7421
//============================================================
7424
//============================================================
7425
// Expose Public Variables
7426
//------------------------------------------------------------
7428
// expose chart's sub-components
7429
chart.dispatch = dispatch;
7430
chart.legend = legend;
7431
chart.lines = lines;
7432
chart.lines2 = lines2;
7434
chart.bars2 = bars2;
7435
chart.xAxis = xAxis;
7436
chart.x2Axis = x2Axis;
7437
chart.y1Axis = y1Axis;
7438
chart.y2Axis = y2Axis;
7439
chart.y3Axis = y3Axis;
7440
chart.y4Axis = y4Axis;
7442
d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
7443
//TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
7444
//d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7446
chart.options = nv.utils.optionsFunc.bind(chart);
7448
chart.x = function(_) {
7449
if (!arguments.length) return getX;
7456
chart.y = function(_) {
7457
if (!arguments.length) return getY;
7464
chart.margin = function(_) {
7465
if (!arguments.length) return margin;
7466
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7467
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7468
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7469
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7473
chart.width = function(_) {
7474
if (!arguments.length) return width;
7479
chart.height = function(_) {
7480
if (!arguments.length) return height;
7485
chart.color = function(_) {
7486
if (!arguments.length) return color;
7487
color = nv.utils.getColor(_);
7488
legend.color(color);
7492
chart.showLegend = function(_) {
7493
if (!arguments.length) return showLegend;
7498
chart.tooltips = function(_) {
7499
if (!arguments.length) return tooltips;
7504
chart.tooltipContent = function(_) {
7505
if (!arguments.length) return tooltip;
7510
chart.noData = function(_) {
7511
if (!arguments.length) return noData;
7516
chart.brushExtent = function(_) {
7517
if (!arguments.length) return brushExtent;
7523
//============================================================
7529
nv.models.multiBar = function() {
7531
//============================================================
7532
// Public Variables with Default Settings
7533
//------------------------------------------------------------
7535
var margin = {top: 0, right: 0, bottom: 0, left: 0}
7538
, x = d3.scale.ordinal()
7539
, y = d3.scale.linear()
7540
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7541
, getX = function(d) { return d.x }
7542
, getY = function(d) { return d.y }
7543
, forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7546
, stackOffset = 'zero' // options include 'silhouette', 'wiggle', 'expand', 'zero', or a custom function
7547
, color = nv.utils.defaultColor()
7549
, barColor = null // adding the ability to set the color for each rather than the whole group
7550
, disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7556
, groupSpacing = 0.1
7557
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
7560
//============================================================
7563
//============================================================
7564
// Private Variables
7565
//------------------------------------------------------------
7567
var x0, y0 //used to store previous scales
7570
//============================================================
7573
function chart(selection) {
7574
selection.each(function(data) {
7575
var availableWidth = width - margin.left - margin.right,
7576
availableHeight = height - margin.top - margin.bottom,
7577
container = d3.select(this);
7579
if(hideable && data.length) hideable = [{
7580
values: data[0].values.map(function(d) {
7590
data = d3.layout.stack()
7591
.offset(stackOffset)
7592
.values(function(d){ return d.values })
7594
(!data.length && hideable ? hideable : data);
7597
//add series index to each data point for reference
7598
data.forEach(function(series, i) {
7599
series.values.forEach(function(point) {
7605
//------------------------------------------------------------
7606
// HACK for negative value stacking
7608
data[0].values.map(function(d,i) {
7609
var posBase = 0, negBase = 0;
7610
data.map(function(d) {
7612
f.size = Math.abs(f.y);
7615
negBase = negBase - f.size;
7618
f.y1 = f.size + posBase;
7619
posBase = posBase + f.size;
7624
//------------------------------------------------------------
7627
// remap and flatten the data for use in calculating the scales' domains
7628
var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7629
data.map(function(d) {
7630
return d.values.map(function(d,i) {
7631
return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7635
x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7636
.rangeBands(xRange || [0, availableWidth], groupSpacing);
7638
//y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
7639
y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return stacked ? (d.y > 0 ? d.y1 : d.y1 + d.y ) : d.y }).concat(forceY)))
7640
.range(yRange || [availableHeight, 0]);
7642
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
7643
if (x.domain()[0] === x.domain()[1])
7645
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7648
if (y.domain()[0] === y.domain()[1])
7650
y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7657
//------------------------------------------------------------
7660
//------------------------------------------------------------
7661
// Setup containers and skeleton of chart
7663
var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
7664
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
7665
var defsEnter = wrapEnter.append('defs');
7666
var gEnter = wrapEnter.append('g');
7667
var g = wrap.select('g')
7669
gEnter.append('g').attr('class', 'nv-groups');
7671
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7673
//------------------------------------------------------------
7677
defsEnter.append('clipPath')
7678
.attr('id', 'nv-edge-clip-' + id)
7680
wrap.select('#nv-edge-clip-' + id + ' rect')
7681
.attr('width', availableWidth)
7682
.attr('height', availableHeight);
7684
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7688
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7689
.data(function(d) { return d }, function(d,i) { return i });
7690
groups.enter().append('g')
7691
.style('stroke-opacity', 1e-6)
7692
.style('fill-opacity', 1e-6);
7695
.selectAll('rect.nv-bar')
7696
.delay(function(d,i) {
7697
return i * delay/ data[0].values.length;
7699
.attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
7703
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7704
.classed('hover', function(d) { return d.hover })
7705
.style('fill', function(d,i){ return color(d, i) })
7706
.style('stroke', function(d,i){ return color(d, i) });
7709
.style('stroke-opacity', 1)
7710
.style('fill-opacity', .75);
7713
var bars = groups.selectAll('rect.nv-bar')
7714
.data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7716
bars.exit().remove();
7719
var barsEnter = bars.enter().append('rect')
7720
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7721
.attr('x', function(d,i,j) {
7722
return stacked ? 0 : (j * x.rangeBand() / data.length )
7724
.attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
7726
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
7727
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7730
.style('fill', function(d,i,j){ return color(d, j, i); })
7731
.style('stroke', function(d,i,j){ return color(d, j, i); })
7732
.on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7733
d3.select(this).classed('hover', true);
7734
dispatch.elementMouseover({
7737
series: data[d.series],
7738
pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
7740
seriesIndex: d.series,
7744
.on('mouseout', function(d,i) {
7745
d3.select(this).classed('hover', false);
7746
dispatch.elementMouseout({
7749
series: data[d.series],
7751
seriesIndex: d.series,
7755
.on('click', function(d,i) {
7756
dispatch.elementClick({
7759
series: data[d.series],
7760
pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
7762
seriesIndex: d.series,
7765
d3.event.stopPropagation();
7767
.on('dblclick', function(d,i) {
7768
dispatch.elementDblClick({
7771
series: data[d.series],
7772
pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
7774
seriesIndex: d.series,
7777
d3.event.stopPropagation();
7780
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7782
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7785
if (!disabled) disabled = data.map(function() { return true });
7787
.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(); })
7788
.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(); });
7794
.delay(function(d,i) {
7796
return i * delay / data[0].values.length;
7798
.attr('y', function(d,i) {
7800
return y((stacked ? d.y1 : 0));
7802
.attr('height', function(d,i) {
7803
return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
7805
.attr('x', function(d,i) {
7806
return stacked ? 0 : (d.series * x.rangeBand() / data.length )
7808
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
7811
.delay(function(d,i) {
7812
return i * delay/ data[0].values.length;
7814
.attr('x', function(d,i) {
7815
return d.series * x.rangeBand() / data.length
7817
.attr('width', x.rangeBand() / data.length)
7818
.attr('y', function(d,i) {
7819
return getY(d,i) < 0 ?
7821
y(0) - y(getY(d,i)) < 1 ?
7825
.attr('height', function(d,i) {
7826
return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7831
//store old scales for use in transitions on update
7841
//============================================================
7842
// Expose Public Variables
7843
//------------------------------------------------------------
7845
chart.dispatch = dispatch;
7847
chart.options = nv.utils.optionsFunc.bind(chart);
7849
chart.x = function(_) {
7850
if (!arguments.length) return getX;
7855
chart.y = function(_) {
7856
if (!arguments.length) return getY;
7861
chart.margin = function(_) {
7862
if (!arguments.length) return margin;
7863
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7864
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7865
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7866
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7870
chart.width = function(_) {
7871
if (!arguments.length) return width;
7876
chart.height = function(_) {
7877
if (!arguments.length) return height;
7882
chart.xScale = function(_) {
7883
if (!arguments.length) return x;
7888
chart.yScale = function(_) {
7889
if (!arguments.length) return y;
7894
chart.xDomain = function(_) {
7895
if (!arguments.length) return xDomain;
7900
chart.yDomain = function(_) {
7901
if (!arguments.length) return yDomain;
7906
chart.xRange = function(_) {
7907
if (!arguments.length) return xRange;
7912
chart.yRange = function(_) {
7913
if (!arguments.length) return yRange;
7918
chart.forceY = function(_) {
7919
if (!arguments.length) return forceY;
7924
chart.stacked = function(_) {
7925
if (!arguments.length) return stacked;
7930
chart.stackOffset = function(_) {
7931
if (!arguments.length) return stackOffset;
7936
chart.clipEdge = function(_) {
7937
if (!arguments.length) return clipEdge;
7942
chart.color = function(_) {
7943
if (!arguments.length) return color;
7944
color = nv.utils.getColor(_);
7948
chart.barColor = function(_) {
7949
if (!arguments.length) return barColor;
7950
barColor = nv.utils.getColor(_);
7954
chart.disabled = function(_) {
7955
if (!arguments.length) return disabled;
7960
chart.id = function(_) {
7961
if (!arguments.length) return id;
7966
chart.hideable = function(_) {
7967
if (!arguments.length) return hideable;
7972
chart.delay = function(_) {
7973
if (!arguments.length) return delay;
7978
chart.groupSpacing = function(_) {
7979
if (!arguments.length) return groupSpacing;
7984
//============================================================
7990
nv.models.multiBarChart = function() {
7992
//============================================================
7993
// Public Variables with Default Settings
7994
//------------------------------------------------------------
7996
var multibar = nv.models.multiBar()
7997
, xAxis = nv.models.axis()
7998
, yAxis = nv.models.axis()
7999
, legend = nv.models.legend()
8000
, controls = nv.models.legend()
8003
var margin = {top: 30, right: 20, bottom: 50, left: 60}
8006
, color = nv.utils.defaultColor()
8007
, showControls = true
8011
, rightAlignYAxis = false
8012
, reduceXTicks = true // if false a tick will show for every data point
8013
, staggerLabels = false
8016
, tooltip = function(key, x, y, e, graph) {
8017
return '<h3>' + key + '</h3>' +
8018
'<p>' + y + ' on ' + x + '</p>'
8020
, x //can be accessed via chart.xScale()
8021
, y //can be accessed via chart.yScale()
8022
, state = { stacked: false }
8023
, defaultState = null
8024
, noData = "No Data Available."
8025
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
8026
, controlWidth = function() { return showControls ? 180 : 0 }
8027
, transitionDuration = 250
8036
.highlightZero(true)
8038
.tickFormat(function(d) { return d })
8041
.orient((rightAlignYAxis) ? 'right' : 'left')
8042
.tickFormat(d3.format(',.1f'))
8045
controls.updateState(false);
8046
//============================================================
8049
//============================================================
8050
// Private Variables
8051
//------------------------------------------------------------
8053
var showTooltip = function(e, offsetElement) {
8054
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8055
top = e.pos[1] + ( offsetElement.offsetTop || 0),
8056
x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8057
y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
8058
content = tooltip(e.series.key, x, y, e, chart);
8060
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
8063
//============================================================
8066
function chart(selection) {
8067
selection.each(function(data) {
8068
var container = d3.select(this),
8071
var availableWidth = (width || parseInt(container.style('width')) || 960)
8072
- margin.left - margin.right,
8073
availableHeight = (height || parseInt(container.style('height')) || 400)
8074
- margin.top - margin.bottom;
8076
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
8077
chart.container = this;
8079
//set state.disabled
8080
state.disabled = data.map(function(d) { return !!d.disabled });
8082
if (!defaultState) {
8085
for (key in state) {
8086
if (state[key] instanceof Array)
8087
defaultState[key] = state[key].slice(0);
8089
defaultState[key] = state[key];
8092
//------------------------------------------------------------
8093
// Display noData message if there's nothing to show.
8095
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8096
var noDataText = container.selectAll('.nv-noData').data([noData]);
8098
noDataText.enter().append('text')
8099
.attr('class', 'nvd3 nv-noData')
8100
.attr('dy', '-.7em')
8101
.style('text-anchor', 'middle');
8104
.attr('x', margin.left + availableWidth / 2)
8105
.attr('y', margin.top + availableHeight / 2)
8106
.text(function(d) { return d });
8110
container.selectAll('.nv-noData').remove();
8113
//------------------------------------------------------------
8116
//------------------------------------------------------------
8119
x = multibar.xScale();
8120
y = multibar.yScale();
8122
//------------------------------------------------------------
8125
//------------------------------------------------------------
8126
// Setup containers and skeleton of chart
8128
var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8129
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8130
var g = wrap.select('g');
8132
gEnter.append('g').attr('class', 'nv-x nv-axis');
8133
gEnter.append('g').attr('class', 'nv-y nv-axis');
8134
gEnter.append('g').attr('class', 'nv-barsWrap');
8135
gEnter.append('g').attr('class', 'nv-legendWrap');
8136
gEnter.append('g').attr('class', 'nv-controlsWrap');
8138
//------------------------------------------------------------
8141
//------------------------------------------------------------
8145
legend.width(availableWidth - controlWidth());
8147
if (multibar.barColor())
8148
data.forEach(function(series,i) {
8149
series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8152
g.select('.nv-legendWrap')
8156
if ( margin.top != legend.height()) {
8157
margin.top = legend.height();
8158
availableHeight = (height || parseInt(container.style('height')) || 400)
8159
- margin.top - margin.bottom;
8162
g.select('.nv-legendWrap')
8163
.attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8166
//------------------------------------------------------------
8169
//------------------------------------------------------------
8173
var controlsData = [
8174
{ key: 'Grouped', disabled: multibar.stacked() },
8175
{ key: 'Stacked', disabled: !multibar.stacked() }
8178
controls.width(controlWidth()).color(['#444', '#444', '#444']);
8179
g.select('.nv-controlsWrap')
8180
.datum(controlsData)
8181
.attr('transform', 'translate(0,' + (-margin.top) +')')
8185
//------------------------------------------------------------
8188
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8190
if (rightAlignYAxis) {
8191
g.select(".nv-y.nv-axis")
8192
.attr("transform", "translate(" + availableWidth + ",0)");
8195
//------------------------------------------------------------
8196
// Main Chart Component(s)
8199
.disabled(data.map(function(series) { return series.disabled }))
8200
.width(availableWidth)
8201
.height(availableHeight)
8202
.color(data.map(function(d,i) {
8203
return d.color || color(d, i);
8204
}).filter(function(d,i) { return !data[i].disabled }))
8207
var barsWrap = g.select('.nv-barsWrap')
8208
.datum(data.filter(function(d) { return !d.disabled }))
8210
barsWrap.transition().call(multibar);
8212
//------------------------------------------------------------
8215
//------------------------------------------------------------
8221
.ticks( availableWidth / 100 )
8222
.tickSize(-availableHeight, 0);
8224
g.select('.nv-x.nv-axis')
8225
.attr('transform', 'translate(0,' + y.range()[0] + ')');
8226
g.select('.nv-x.nv-axis').transition()
8229
var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8232
.selectAll('line, text')
8233
.style('opacity', 1)
8235
if (staggerLabels) {
8236
var getTranslate = function(x,y) {
8237
return "translate(" + x + "," + y + ")";
8240
var staggerUp = 5, staggerDown = 17; //pixels to stagger by
8244
.attr('transform', function(d,i,j) {
8245
return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8248
var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8249
g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8250
.attr("transform", function(d,i) {
8251
return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8257
.filter(function(d,i) {
8258
return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8260
.selectAll('text, line')
8261
.style('opacity', 0);
8265
.selectAll('.tick text')
8266
.attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8267
.style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8269
g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8270
.style('opacity', 1);
8277
.ticks( availableHeight / 36 )
8278
.tickSize( -availableWidth, 0);
8280
g.select('.nv-y.nv-axis').transition()
8285
//------------------------------------------------------------
8289
//============================================================
8290
// Event Handling/Dispatching (in chart's scope)
8291
//------------------------------------------------------------
8293
legend.dispatch.on('stateChange', function(newState) {
8295
dispatch.stateChange(state);
8299
controls.dispatch.on('legendClick', function(d,i) {
8300
if (!d.disabled) return;
8301
controlsData = controlsData.map(function(s) {
8309
multibar.stacked(false);
8312
multibar.stacked(true);
8316
state.stacked = multibar.stacked();
8317
dispatch.stateChange(state);
8322
dispatch.on('tooltipShow', function(e) {
8323
if (tooltips) showTooltip(e, that.parentNode)
8326
// Update chart from a state object passed to event handler
8327
dispatch.on('changeState', function(e) {
8329
if (typeof e.disabled !== 'undefined') {
8330
data.forEach(function(series,i) {
8331
series.disabled = e.disabled[i];
8334
state.disabled = e.disabled;
8337
if (typeof e.stacked !== 'undefined') {
8338
multibar.stacked(e.stacked);
8339
state.stacked = e.stacked;
8345
//============================================================
8354
//============================================================
8355
// Event Handling/Dispatching (out of chart's scope)
8356
//------------------------------------------------------------
8358
multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8359
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8360
dispatch.tooltipShow(e);
8363
multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8364
dispatch.tooltipHide(e);
8366
dispatch.on('tooltipHide', function() {
8367
if (tooltips) nv.tooltip.cleanup();
8370
//============================================================
8373
//============================================================
8374
// Expose Public Variables
8375
//------------------------------------------------------------
8377
// expose chart's sub-components
8378
chart.dispatch = dispatch;
8379
chart.multibar = multibar;
8380
chart.legend = legend;
8381
chart.xAxis = xAxis;
8382
chart.yAxis = yAxis;
8384
d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge',
8385
'id', 'stacked', 'stackOffset', 'delay', 'barColor','groupSpacing');
8387
chart.options = nv.utils.optionsFunc.bind(chart);
8389
chart.margin = function(_) {
8390
if (!arguments.length) return margin;
8391
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8392
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8393
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8394
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8398
chart.width = function(_) {
8399
if (!arguments.length) return width;
8404
chart.height = function(_) {
8405
if (!arguments.length) return height;
8410
chart.color = function(_) {
8411
if (!arguments.length) return color;
8412
color = nv.utils.getColor(_);
8413
legend.color(color);
8417
chart.showControls = function(_) {
8418
if (!arguments.length) return showControls;
8423
chart.showLegend = function(_) {
8424
if (!arguments.length) return showLegend;
8429
chart.showXAxis = function(_) {
8430
if (!arguments.length) return showXAxis;
8435
chart.showYAxis = function(_) {
8436
if (!arguments.length) return showYAxis;
8441
chart.rightAlignYAxis = function(_) {
8442
if(!arguments.length) return rightAlignYAxis;
8443
rightAlignYAxis = _;
8444
yAxis.orient( (_) ? 'right' : 'left');
8448
chart.reduceXTicks= function(_) {
8449
if (!arguments.length) return reduceXTicks;
8454
chart.rotateLabels = function(_) {
8455
if (!arguments.length) return rotateLabels;
8460
chart.staggerLabels = function(_) {
8461
if (!arguments.length) return staggerLabels;
8466
chart.tooltip = function(_) {
8467
if (!arguments.length) return tooltip;
8472
chart.tooltips = function(_) {
8473
if (!arguments.length) return tooltips;
8478
chart.tooltipContent = function(_) {
8479
if (!arguments.length) return tooltip;
8484
chart.state = function(_) {
8485
if (!arguments.length) return state;
8490
chart.defaultState = function(_) {
8491
if (!arguments.length) return defaultState;
8496
chart.noData = function(_) {
8497
if (!arguments.length) return noData;
8502
chart.transitionDuration = function(_) {
8503
if (!arguments.length) return transitionDuration;
8504
transitionDuration = _;
8508
//============================================================
8514
nv.models.multiBarHorizontal = function() {
8516
//============================================================
8517
// Public Variables with Default Settings
8518
//------------------------------------------------------------
8520
var margin = {top: 0, right: 0, bottom: 0, left: 0}
8523
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8524
, x = d3.scale.ordinal()
8525
, y = d3.scale.linear()
8526
, getX = function(d) { return d.x }
8527
, getY = function(d) { return d.y }
8528
, forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
8529
, color = nv.utils.defaultColor()
8530
, barColor = null // adding the ability to set the color for each rather than the whole group
8531
, disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
8533
, showValues = false
8534
, showBarLabels = false
8536
, valueFormat = d3.format(',.2f')
8542
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8545
//============================================================
8548
//============================================================
8549
// Private Variables
8550
//------------------------------------------------------------
8552
var x0, y0 //used to store previous scales
8555
//============================================================
8558
function chart(selection) {
8559
selection.each(function(data) {
8560
var availableWidth = width - margin.left - margin.right,
8561
availableHeight = height - margin.top - margin.bottom,
8562
container = d3.select(this);
8566
data = d3.layout.stack()
8568
.values(function(d){ return d.values })
8573
//add series index to each data point for reference
8574
data.forEach(function(series, i) {
8575
series.values.forEach(function(point) {
8582
//------------------------------------------------------------
8583
// HACK for negative value stacking
8585
data[0].values.map(function(d,i) {
8586
var posBase = 0, negBase = 0;
8587
data.map(function(d) {
8589
f.size = Math.abs(f.y);
8591
f.y1 = negBase - f.size;
8592
negBase = negBase - f.size;
8596
posBase = posBase + f.size;
8603
//------------------------------------------------------------
8606
// remap and flatten the data for use in calculating the scales' domains
8607
var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8608
data.map(function(d) {
8609
return d.values.map(function(d,i) {
8610
return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8614
x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8615
.rangeBands(xRange || [0, availableHeight], .1);
8617
//y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
8618
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)))
8620
if (showValues && !stacked)
8621
y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
8623
y.range(yRange || [0, availableWidth]);
8626
y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8628
//------------------------------------------------------------
8631
//------------------------------------------------------------
8632
// Setup containers and skeleton of chart
8634
var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
8635
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
8636
var defsEnter = wrapEnter.append('defs');
8637
var gEnter = wrapEnter.append('g');
8638
var g = wrap.select('g');
8640
gEnter.append('g').attr('class', 'nv-groups');
8642
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8644
//------------------------------------------------------------
8648
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8649
.data(function(d) { return d }, function(d,i) { return i });
8650
groups.enter().append('g')
8651
.style('stroke-opacity', 1e-6)
8652
.style('fill-opacity', 1e-6);
8653
groups.exit().transition()
8654
.style('stroke-opacity', 1e-6)
8655
.style('fill-opacity', 1e-6)
8658
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8659
.classed('hover', function(d) { return d.hover })
8660
.style('fill', function(d,i){ return color(d, i) })
8661
.style('stroke', function(d,i){ return color(d, i) });
8663
.style('stroke-opacity', 1)
8664
.style('fill-opacity', .75);
8667
var bars = groups.selectAll('g.nv-bar')
8668
.data(function(d) { return d.values });
8670
bars.exit().remove();
8673
var barsEnter = bars.enter().append('g')
8674
.attr('transform', function(d,i,j) {
8675
return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
8678
barsEnter.append('rect')
8680
.attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8683
.on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8684
d3.select(this).classed('hover', true);
8685
dispatch.elementMouseover({
8688
series: data[d.series],
8689
pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
8691
seriesIndex: d.series,
8695
.on('mouseout', function(d,i) {
8696
d3.select(this).classed('hover', false);
8697
dispatch.elementMouseout({
8700
series: data[d.series],
8702
seriesIndex: d.series,
8706
.on('click', function(d,i) {
8707
dispatch.elementClick({
8710
series: data[d.series],
8711
pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
8713
seriesIndex: d.series,
8716
d3.event.stopPropagation();
8718
.on('dblclick', function(d,i) {
8719
dispatch.elementDblClick({
8722
series: data[d.series],
8723
pos: [x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length), y(getY(d,i) + (stacked ? d.y0 : 0))], // TODO: Figure out why the value appears to be shifted
8725
seriesIndex: d.series,
8728
d3.event.stopPropagation();
8732
barsEnter.append('text');
8734
if (showValues && !stacked) {
8736
.attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
8737
.attr('y', x.rangeBand() / (data.length * 2))
8738
.attr('dy', '.32em')
8739
.text(function(d,i) { return valueFormat(getY(d,i)) })
8742
.attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8744
bars.selectAll('text').text('');
8747
if (showBarLabels && !stacked) {
8748
barsEnter.append('text').classed('nv-bar-label',true);
8749
bars.select('text.nv-bar-label')
8750
.attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'start' : 'end' })
8751
.attr('y', x.rangeBand() / (data.length * 2))
8752
.attr('dy', '.32em')
8753
.text(function(d,i) { return getX(d,i) });
8755
.select('text.nv-bar-label')
8756
.attr('x', function(d,i) { return getY(d,i) < 0 ? y(0) - y(getY(d,i)) + 4 : -4 });
8759
bars.selectAll('text.nv-bar-label').text('');
8763
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8766
if (!disabled) disabled = data.map(function() { return true });
8768
.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(); })
8769
.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(); });
8774
.attr('transform', function(d,i) {
8775
return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8778
.attr('width', function(d,i) {
8779
return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8781
.attr('height', x.rangeBand() );
8784
.attr('transform', function(d,i) {
8785
//TODO: stacked must be all positive or all negative, not both?
8786
return 'translate(' +
8787
(getY(d,i) < 0 ? y(getY(d,i)) : y(0))
8789
(d.series * x.rangeBand() / data.length
8795
.attr('height', x.rangeBand() / data.length )
8796
.attr('width', function(d,i) {
8797
return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
8801
//store old scales for use in transitions on update
8811
//============================================================
8812
// Expose Public Variables
8813
//------------------------------------------------------------
8815
chart.dispatch = dispatch;
8817
chart.options = nv.utils.optionsFunc.bind(chart);
8819
chart.x = function(_) {
8820
if (!arguments.length) return getX;
8825
chart.y = function(_) {
8826
if (!arguments.length) return getY;
8831
chart.margin = function(_) {
8832
if (!arguments.length) return margin;
8833
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8834
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8835
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8836
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8840
chart.width = function(_) {
8841
if (!arguments.length) return width;
8846
chart.height = function(_) {
8847
if (!arguments.length) return height;
8852
chart.xScale = function(_) {
8853
if (!arguments.length) return x;
8858
chart.yScale = function(_) {
8859
if (!arguments.length) return y;
8864
chart.xDomain = function(_) {
8865
if (!arguments.length) return xDomain;
8870
chart.yDomain = function(_) {
8871
if (!arguments.length) return yDomain;
8876
chart.xRange = function(_) {
8877
if (!arguments.length) return xRange;
8882
chart.yRange = function(_) {
8883
if (!arguments.length) return yRange;
8888
chart.forceY = function(_) {
8889
if (!arguments.length) return forceY;
8894
chart.stacked = function(_) {
8895
if (!arguments.length) return stacked;
8900
chart.color = function(_) {
8901
if (!arguments.length) return color;
8902
color = nv.utils.getColor(_);
8906
chart.barColor = function(_) {
8907
if (!arguments.length) return barColor;
8908
barColor = nv.utils.getColor(_);
8912
chart.disabled = function(_) {
8913
if (!arguments.length) return disabled;
8918
chart.id = function(_) {
8919
if (!arguments.length) return id;
8924
chart.delay = function(_) {
8925
if (!arguments.length) return delay;
8930
chart.showValues = function(_) {
8931
if (!arguments.length) return showValues;
8936
chart.showBarLabels = function(_) {
8937
if (!arguments.length) return showBarLabels;
8943
chart.valueFormat= function(_) {
8944
if (!arguments.length) return valueFormat;
8949
chart.valuePadding = function(_) {
8950
if (!arguments.length) return valuePadding;
8955
//============================================================
8961
nv.models.multiBarHorizontalChart = function() {
8963
//============================================================
8964
// Public Variables with Default Settings
8965
//------------------------------------------------------------
8967
var multibar = nv.models.multiBarHorizontal()
8968
, xAxis = nv.models.axis()
8969
, yAxis = nv.models.axis()
8970
, legend = nv.models.legend().height(30)
8971
, controls = nv.models.legend().height(30)
8974
var margin = {top: 30, right: 20, bottom: 50, left: 60}
8977
, color = nv.utils.defaultColor()
8978
, showControls = true
8984
, tooltip = function(key, x, y, e, graph) {
8985
return '<h3>' + key + ' - ' + x + '</h3>' +
8988
, x //can be accessed via chart.xScale()
8989
, y //can be accessed via chart.yScale()
8990
, state = { stacked: stacked }
8991
, defaultState = null
8992
, noData = 'No Data Available.'
8993
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
8994
, controlWidth = function() { return showControls ? 180 : 0 }
8995
, transitionDuration = 250
9004
.highlightZero(false)
9006
.tickFormat(function(d) { return d })
9010
.tickFormat(d3.format(',.1f'))
9013
controls.updateState(false);
9014
//============================================================
9017
//============================================================
9018
// Private Variables
9019
//------------------------------------------------------------
9021
var showTooltip = function(e, offsetElement) {
9022
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9023
top = e.pos[1] + ( offsetElement.offsetTop || 0),
9024
x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
9025
y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
9026
content = tooltip(e.series.key, x, y, e, chart);
9028
nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
9031
//============================================================
9034
function chart(selection) {
9035
selection.each(function(data) {
9036
var container = d3.select(this),
9039
var availableWidth = (width || parseInt(container.style('width')) || 960)
9040
- margin.left - margin.right,
9041
availableHeight = (height || parseInt(container.style('height')) || 400)
9042
- margin.top - margin.bottom;
9044
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
9045
chart.container = this;
9047
//set state.disabled
9048
state.disabled = data.map(function(d) { return !!d.disabled });
9050
if (!defaultState) {
9053
for (key in state) {
9054
if (state[key] instanceof Array)
9055
defaultState[key] = state[key].slice(0);
9057
defaultState[key] = state[key];
9061
//------------------------------------------------------------
9062
// Display No Data message if there's nothing to show.
9064
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
9065
var noDataText = container.selectAll('.nv-noData').data([noData]);
9067
noDataText.enter().append('text')
9068
.attr('class', 'nvd3 nv-noData')
9069
.attr('dy', '-.7em')
9070
.style('text-anchor', 'middle');
9073
.attr('x', margin.left + availableWidth / 2)
9074
.attr('y', margin.top + availableHeight / 2)
9075
.text(function(d) { return d });
9079
container.selectAll('.nv-noData').remove();
9082
//------------------------------------------------------------
9085
//------------------------------------------------------------
9088
x = multibar.xScale();
9089
y = multibar.yScale();
9091
//------------------------------------------------------------
9094
//------------------------------------------------------------
9095
// Setup containers and skeleton of chart
9097
var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
9098
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
9099
var g = wrap.select('g');
9101
gEnter.append('g').attr('class', 'nv-x nv-axis');
9102
gEnter.append('g').attr('class', 'nv-y nv-axis')
9103
.append('g').attr('class', 'nv-zeroLine')
9105
gEnter.append('g').attr('class', 'nv-barsWrap');
9106
gEnter.append('g').attr('class', 'nv-legendWrap');
9107
gEnter.append('g').attr('class', 'nv-controlsWrap');
9109
//------------------------------------------------------------
9112
//------------------------------------------------------------
9116
legend.width(availableWidth - controlWidth());
9118
if (multibar.barColor())
9119
data.forEach(function(series,i) {
9120
series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
9123
g.select('.nv-legendWrap')
9127
if ( margin.top != legend.height()) {
9128
margin.top = legend.height();
9129
availableHeight = (height || parseInt(container.style('height')) || 400)
9130
- margin.top - margin.bottom;
9133
g.select('.nv-legendWrap')
9134
.attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
9137
//------------------------------------------------------------
9140
//------------------------------------------------------------
9144
var controlsData = [
9145
{ key: 'Grouped', disabled: multibar.stacked() },
9146
{ key: 'Stacked', disabled: !multibar.stacked() }
9149
controls.width(controlWidth()).color(['#444', '#444', '#444']);
9150
g.select('.nv-controlsWrap')
9151
.datum(controlsData)
9152
.attr('transform', 'translate(0,' + (-margin.top) +')')
9156
//------------------------------------------------------------
9159
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9162
//------------------------------------------------------------
9163
// Main Chart Component(s)
9166
.disabled(data.map(function(series) { return series.disabled }))
9167
.width(availableWidth)
9168
.height(availableHeight)
9169
.color(data.map(function(d,i) {
9170
return d.color || color(d, i);
9171
}).filter(function(d,i) { return !data[i].disabled }))
9174
var barsWrap = g.select('.nv-barsWrap')
9175
.datum(data.filter(function(d) { return !d.disabled }))
9177
barsWrap.transition().call(multibar);
9179
//------------------------------------------------------------
9182
//------------------------------------------------------------
9188
.ticks( availableHeight / 24 )
9189
.tickSize(-availableWidth, 0);
9191
g.select('.nv-x.nv-axis').transition()
9194
var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9197
.selectAll('line, text');
9203
.ticks( availableWidth / 100 )
9204
.tickSize( -availableHeight, 0);
9206
g.select('.nv-y.nv-axis')
9207
.attr('transform', 'translate(0,' + availableHeight + ')');
9208
g.select('.nv-y.nv-axis').transition()
9213
g.select(".nv-zeroLine line")
9217
.attr("y2", -availableHeight)
9220
//------------------------------------------------------------
9224
//============================================================
9225
// Event Handling/Dispatching (in chart's scope)
9226
//------------------------------------------------------------
9228
legend.dispatch.on('stateChange', function(newState) {
9230
dispatch.stateChange(state);
9234
controls.dispatch.on('legendClick', function(d,i) {
9235
if (!d.disabled) return;
9236
controlsData = controlsData.map(function(s) {
9244
multibar.stacked(false);
9247
multibar.stacked(true);
9251
state.stacked = multibar.stacked();
9252
dispatch.stateChange(state);
9257
dispatch.on('tooltipShow', function(e) {
9258
if (tooltips) showTooltip(e, that.parentNode);
9261
// Update chart from a state object passed to event handler
9262
dispatch.on('changeState', function(e) {
9264
if (typeof e.disabled !== 'undefined') {
9265
data.forEach(function(series,i) {
9266
series.disabled = e.disabled[i];
9269
state.disabled = e.disabled;
9272
if (typeof e.stacked !== 'undefined') {
9273
multibar.stacked(e.stacked);
9274
state.stacked = e.stacked;
9279
//============================================================
9288
//============================================================
9289
// Event Handling/Dispatching (out of chart's scope)
9290
//------------------------------------------------------------
9292
multibar.dispatch.on('elementMouseover.tooltip', function(e) {
9293
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9294
dispatch.tooltipShow(e);
9297
multibar.dispatch.on('elementMouseout.tooltip', function(e) {
9298
dispatch.tooltipHide(e);
9300
dispatch.on('tooltipHide', function() {
9301
if (tooltips) nv.tooltip.cleanup();
9304
//============================================================
9307
//============================================================
9308
// Expose Public Variables
9309
//------------------------------------------------------------
9311
// expose chart's sub-components
9312
chart.dispatch = dispatch;
9313
chart.multibar = multibar;
9314
chart.legend = legend;
9315
chart.xAxis = xAxis;
9316
chart.yAxis = yAxis;
9318
d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY',
9319
'clipEdge', 'id', 'delay', 'showValues','showBarLabels', 'valueFormat', 'stacked', 'barColor');
9321
chart.options = nv.utils.optionsFunc.bind(chart);
9323
chart.margin = function(_) {
9324
if (!arguments.length) return margin;
9325
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
9326
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
9327
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9328
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
9332
chart.width = function(_) {
9333
if (!arguments.length) return width;
9338
chart.height = function(_) {
9339
if (!arguments.length) return height;
9344
chart.color = function(_) {
9345
if (!arguments.length) return color;
9346
color = nv.utils.getColor(_);
9347
legend.color(color);
9351
chart.showControls = function(_) {
9352
if (!arguments.length) return showControls;
9357
chart.showLegend = function(_) {
9358
if (!arguments.length) return showLegend;
9363
chart.showXAxis = function(_) {
9364
if (!arguments.length) return showXAxis;
9369
chart.showYAxis = function(_) {
9370
if (!arguments.length) return showYAxis;
9375
chart.tooltip = function(_) {
9376
if (!arguments.length) return tooltip;
9381
chart.tooltips = function(_) {
9382
if (!arguments.length) return tooltips;
9387
chart.tooltipContent = function(_) {
9388
if (!arguments.length) return tooltip;
9393
chart.state = function(_) {
9394
if (!arguments.length) return state;
9399
chart.defaultState = function(_) {
9400
if (!arguments.length) return defaultState;
9405
chart.noData = function(_) {
9406
if (!arguments.length) return noData;
9411
chart.transitionDuration = function(_) {
9412
if (!arguments.length) return transitionDuration;
9413
transitionDuration = _;
9416
//============================================================
9421
nv.models.multiChart = function() {
9423
//============================================================
9424
// Public Variables with Default Settings
9425
//------------------------------------------------------------
9427
var margin = {top: 30, right: 20, bottom: 50, left: 60},
9428
color = d3.scale.category20().range(),
9433
tooltip = function(key, x, y, e, graph) {
9434
return '<h3>' + key + '</h3>' +
9435
'<p>' + y + ' at ' + x + '</p>'
9441
; //can be accessed via chart.lines.[x/y]Scale()
9443
//============================================================
9444
// Private Variables
9445
//------------------------------------------------------------
9447
var x = d3.scale.linear(),
9448
yScale1 = d3.scale.linear(),
9449
yScale2 = d3.scale.linear(),
9451
lines1 = nv.models.line().yScale(yScale1),
9452
lines2 = nv.models.line().yScale(yScale2),
9454
bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9455
bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9457
stack1 = nv.models.stackedArea().yScale(yScale1),
9458
stack2 = nv.models.stackedArea().yScale(yScale2),
9460
xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9461
yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9462
yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
9464
legend = nv.models.legend().height(30),
9465
dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
9467
var showTooltip = function(e, offsetElement) {
9468
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9469
top = e.pos[1] + ( offsetElement.offsetTop || 0),
9470
x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
9471
y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
9472
content = tooltip(e.series.key, x, y, e, chart);
9474
nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
9477
function chart(selection) {
9478
selection.each(function(data) {
9479
var container = d3.select(this),
9482
chart.update = function() { container.transition().call(chart); };
9483
chart.container = this;
9485
var availableWidth = (width || parseInt(container.style('width')) || 960)
9486
- margin.left - margin.right,
9487
availableHeight = (height || parseInt(container.style('height')) || 400)
9488
- margin.top - margin.bottom;
9490
var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
9491
var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
9492
var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
9493
var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
9494
var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
9495
var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
9497
var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9499
return d.values.map(function(d,i) {
9500
return { x: d.x, y: d.y }
9504
var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9506
return d.values.map(function(d,i) {
9507
return { x: d.x, y: d.y }
9511
x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9512
.range([0, availableWidth]);
9514
var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9515
var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9517
gEnter.append('g').attr('class', 'x axis');
9518
gEnter.append('g').attr('class', 'y1 axis');
9519
gEnter.append('g').attr('class', 'y2 axis');
9520
gEnter.append('g').attr('class', 'lines1Wrap');
9521
gEnter.append('g').attr('class', 'lines2Wrap');
9522
gEnter.append('g').attr('class', 'bars1Wrap');
9523
gEnter.append('g').attr('class', 'bars2Wrap');
9524
gEnter.append('g').attr('class', 'stack1Wrap');
9525
gEnter.append('g').attr('class', 'stack2Wrap');
9526
gEnter.append('g').attr('class', 'legendWrap');
9528
var g = wrap.select('g');
9531
legend.width( availableWidth / 2 );
9533
g.select('.legendWrap')
9534
.datum(data.map(function(series) {
9535
series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
9536
series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
9541
if ( margin.top != legend.height()) {
9542
margin.top = legend.height();
9543
availableHeight = (height || parseInt(container.style('height')) || 400)
9544
- margin.top - margin.bottom;
9547
g.select('.legendWrap')
9548
.attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
9553
.width(availableWidth)
9554
.height(availableHeight)
9555
.interpolate("monotone")
9556
.color(data.map(function(d,i) {
9557
return d.color || color[i % color.length];
9558
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
9561
.width(availableWidth)
9562
.height(availableHeight)
9563
.interpolate("monotone")
9564
.color(data.map(function(d,i) {
9565
return d.color || color[i % color.length];
9566
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
9569
.width(availableWidth)
9570
.height(availableHeight)
9571
.color(data.map(function(d,i) {
9572
return d.color || color[i % color.length];
9573
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
9576
.width(availableWidth)
9577
.height(availableHeight)
9578
.color(data.map(function(d,i) {
9579
return d.color || color[i % color.length];
9580
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
9583
.width(availableWidth)
9584
.height(availableHeight)
9585
.color(data.map(function(d,i) {
9586
return d.color || color[i % color.length];
9587
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9590
.width(availableWidth)
9591
.height(availableHeight)
9592
.color(data.map(function(d,i) {
9593
return d.color || color[i % color.length];
9594
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9596
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9599
var lines1Wrap = g.select('.lines1Wrap')
9601
var bars1Wrap = g.select('.bars1Wrap')
9603
var stack1Wrap = g.select('.stack1Wrap')
9606
var lines2Wrap = g.select('.lines2Wrap')
9608
var bars2Wrap = g.select('.bars2Wrap')
9610
var stack2Wrap = g.select('.stack2Wrap')
9613
var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
9614
return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9615
}).concat([{x:0, y:0}]) : []
9616
var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
9617
return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9618
}).concat([{x:0, y:0}]) : []
9620
yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9621
.range([0, availableHeight])
9623
yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9624
.range([0, availableHeight])
9626
lines1.yDomain(yScale1.domain())
9627
bars1.yDomain(yScale1.domain())
9628
stack1.yDomain(yScale1.domain())
9630
lines2.yDomain(yScale2.domain())
9631
bars2.yDomain(yScale2.domain())
9632
stack2.yDomain(yScale2.domain())
9634
if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9635
if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9637
if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9638
if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9640
if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9641
if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9646
.ticks( availableWidth / 100 )
9647
.tickSize(-availableHeight, 0);
9650
.attr('transform', 'translate(0,' + availableHeight + ')');
9651
d3.transition(g.select('.x.axis'))
9655
.ticks( availableHeight / 36 )
9656
.tickSize( -availableWidth, 0);
9659
d3.transition(g.select('.y1.axis'))
9663
.ticks( availableHeight / 36 )
9664
.tickSize( -availableWidth, 0);
9666
d3.transition(g.select('.y2.axis'))
9669
g.select('.y2.axis')
9670
.style('opacity', series2.length ? 1 : 0)
9671
.attr('transform', 'translate(' + x.range()[1] + ',0)');
9673
legend.dispatch.on('stateChange', function(newState) {
9677
dispatch.on('tooltipShow', function(e) {
9678
if (tooltips) showTooltip(e, that.parentNode);
9687
//============================================================
9688
// Event Handling/Dispatching (out of chart's scope)
9689
//------------------------------------------------------------
9691
lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9692
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9693
dispatch.tooltipShow(e);
9696
lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9697
dispatch.tooltipHide(e);
9700
lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9701
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9702
dispatch.tooltipShow(e);
9705
lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9706
dispatch.tooltipHide(e);
9709
bars1.dispatch.on('elementMouseover.tooltip', function(e) {
9710
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9711
dispatch.tooltipShow(e);
9714
bars1.dispatch.on('elementMouseout.tooltip', function(e) {
9715
dispatch.tooltipHide(e);
9718
bars2.dispatch.on('elementMouseover.tooltip', function(e) {
9719
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9720
dispatch.tooltipShow(e);
9723
bars2.dispatch.on('elementMouseout.tooltip', function(e) {
9724
dispatch.tooltipHide(e);
9727
stack1.dispatch.on('tooltipShow', function(e) {
9728
//disable tooltips when value ~= 0
9729
//// TODO: consider removing points from voronoi that have 0 value instead of this hack
9730
if (!Math.round(stack1.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
9731
setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9735
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9736
dispatch.tooltipShow(e);
9739
stack1.dispatch.on('tooltipHide', function(e) {
9740
dispatch.tooltipHide(e);
9743
stack2.dispatch.on('tooltipShow', function(e) {
9744
//disable tooltips when value ~= 0
9745
//// TODO: consider removing points from voronoi that have 0 value instead of this hack
9746
if (!Math.round(stack2.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
9747
setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9751
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9752
dispatch.tooltipShow(e);
9755
stack2.dispatch.on('tooltipHide', function(e) {
9756
dispatch.tooltipHide(e);
9759
lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9760
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9761
dispatch.tooltipShow(e);
9764
lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9765
dispatch.tooltipHide(e);
9768
lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9769
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9770
dispatch.tooltipShow(e);
9773
lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9774
dispatch.tooltipHide(e);
9777
dispatch.on('tooltipHide', function() {
9778
if (tooltips) nv.tooltip.cleanup();
9783
//============================================================
9784
// Global getters and setters
9785
//------------------------------------------------------------
9787
chart.dispatch = dispatch;
9788
chart.lines1 = lines1;
9789
chart.lines2 = lines2;
9790
chart.bars1 = bars1;
9791
chart.bars2 = bars2;
9792
chart.stack1 = stack1;
9793
chart.stack2 = stack2;
9794
chart.xAxis = xAxis;
9795
chart.yAxis1 = yAxis1;
9796
chart.yAxis2 = yAxis2;
9797
chart.options = nv.utils.optionsFunc.bind(chart);
9799
chart.x = function(_) {
9800
if (!arguments.length) return getX;
9807
chart.y = function(_) {
9808
if (!arguments.length) return getY;
9815
chart.yDomain1 = function(_) {
9816
if (!arguments.length) return yDomain1;
9821
chart.yDomain2 = function(_) {
9822
if (!arguments.length) return yDomain2;
9827
chart.margin = function(_) {
9828
if (!arguments.length) return margin;
9833
chart.width = function(_) {
9834
if (!arguments.length) return width;
9839
chart.height = function(_) {
9840
if (!arguments.length) return height;
9845
chart.color = function(_) {
9846
if (!arguments.length) return color;
9852
chart.showLegend = function(_) {
9853
if (!arguments.length) return showLegend;
9858
chart.tooltips = function(_) {
9859
if (!arguments.length) return tooltips;
9864
chart.tooltipContent = function(_) {
9865
if (!arguments.length) return tooltip;
9874
nv.models.ohlcBar = function() {
9876
//============================================================
9877
// Public Variables with Default Settings
9878
//------------------------------------------------------------
9880
var margin = {top: 0, right: 0, bottom: 0, left: 0}
9883
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9884
, x = d3.scale.linear()
9885
, y = d3.scale.linear()
9886
, getX = function(d) { return d.x }
9887
, getY = function(d) { return d.y }
9888
, getOpen = function(d) { return d.open }
9889
, getClose = function(d) { return d.close }
9890
, getHigh = function(d) { return d.high }
9891
, getLow = function(d) { return d.low }
9894
, padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9896
, color = nv.utils.defaultColor()
9901
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
9904
//============================================================
9906
//============================================================
9907
// Private Variables
9908
//------------------------------------------------------------
9910
//TODO: store old scales for transitions
9912
//============================================================
9915
function chart(selection) {
9916
selection.each(function(data) {
9917
var availableWidth = width - margin.left - margin.right,
9918
availableHeight = height - margin.top - margin.bottom,
9919
container = d3.select(this);
9922
//------------------------------------------------------------
9925
x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9928
x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
9930
x.range(xRange || [0, availableWidth]);
9932
y .domain(yDomain || [
9933
d3.min(data[0].values.map(getLow).concat(forceY)),
9934
d3.max(data[0].values.map(getHigh).concat(forceY))
9936
.range(yRange || [availableHeight, 0]);
9938
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9939
if (x.domain()[0] === x.domain()[1])
9941
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9944
if (y.domain()[0] === y.domain()[1])
9946
y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9949
//------------------------------------------------------------
9952
//------------------------------------------------------------
9953
// Setup containers and skeleton of chart
9955
var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
9956
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
9957
var defsEnter = wrapEnter.append('defs');
9958
var gEnter = wrapEnter.append('g');
9959
var g = wrap.select('g');
9961
gEnter.append('g').attr('class', 'nv-ticks');
9963
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9965
//------------------------------------------------------------
9969
.on('click', function(d,i) {
9970
dispatch.chartClick({
9979
defsEnter.append('clipPath')
9980
.attr('id', 'nv-chart-clip-path-' + id)
9983
wrap.select('#nv-chart-clip-path-' + id + ' rect')
9984
.attr('width', availableWidth)
9985
.attr('height', availableHeight);
9987
g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9991
var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9992
.data(function(d) { return d });
9994
ticks.exit().remove();
9997
var ticksEnter = ticks.enter().append('path')
9998
.attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9999
.attr('d', function(d,i) {
10000
var w = (availableWidth / data[0].values.length) * .9;
10009
+ (y(getLow(d,i)) - y(getOpen(d,i)))
10011
+ (y(getClose(d,i))
10019
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
10020
//.attr('fill', function(d,i) { return color[0]; })
10021
//.attr('stroke', function(d,i) { return color[0]; })
10023
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
10024
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
10025
.on('mouseover', function(d,i) {
10026
d3.select(this).classed('hover', true);
10027
dispatch.elementMouseover({
10030
pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
10037
.on('mouseout', function(d,i) {
10038
d3.select(this).classed('hover', false);
10039
dispatch.elementMouseout({
10047
.on('click', function(d,i) {
10048
dispatch.elementClick({
10053
pos: [x(getX(d,i)), y(getY(d,i))],
10057
d3.event.stopPropagation();
10059
.on('dblclick', function(d,i) {
10060
dispatch.elementDblClick({
10065
pos: [x(getX(d,i)), y(getY(d,i))],
10069
d3.event.stopPropagation();
10073
.attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
10074
d3.transition(ticks)
10075
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
10076
.attr('d', function(d,i) {
10077
var w = (availableWidth / data[0].values.length) * .9;
10089
+ (y(getClose(d,i))
10097
//.attr('width', (availableWidth / data[0].values.length) * .9 )
10100
//d3.transition(ticks)
10101
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
10102
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
10103
//.order(); // not sure if this makes any sense for this model
10111
//============================================================
10112
// Expose Public Variables
10113
//------------------------------------------------------------
10115
chart.dispatch = dispatch;
10117
chart.options = nv.utils.optionsFunc.bind(chart);
10119
chart.x = function(_) {
10120
if (!arguments.length) return getX;
10125
chart.y = function(_) {
10126
if (!arguments.length) return getY;
10131
chart.open = function(_) {
10132
if (!arguments.length) return getOpen;
10137
chart.close = function(_) {
10138
if (!arguments.length) return getClose;
10143
chart.high = function(_) {
10144
if (!arguments.length) return getHigh;
10149
chart.low = function(_) {
10150
if (!arguments.length) return getLow;
10155
chart.margin = function(_) {
10156
if (!arguments.length) return margin;
10157
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10158
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10159
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10160
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10164
chart.width = function(_) {
10165
if (!arguments.length) return width;
10170
chart.height = function(_) {
10171
if (!arguments.length) return height;
10176
chart.xScale = function(_) {
10177
if (!arguments.length) return x;
10182
chart.yScale = function(_) {
10183
if (!arguments.length) return y;
10188
chart.xDomain = function(_) {
10189
if (!arguments.length) return xDomain;
10194
chart.yDomain = function(_) {
10195
if (!arguments.length) return yDomain;
10200
chart.xRange = function(_) {
10201
if (!arguments.length) return xRange;
10206
chart.yRange = function(_) {
10207
if (!arguments.length) return yRange;
10212
chart.forceX = function(_) {
10213
if (!arguments.length) return forceX;
10218
chart.forceY = function(_) {
10219
if (!arguments.length) return forceY;
10224
chart.padData = function(_) {
10225
if (!arguments.length) return padData;
10230
chart.clipEdge = function(_) {
10231
if (!arguments.length) return clipEdge;
10236
chart.color = function(_) {
10237
if (!arguments.length) return color;
10238
color = nv.utils.getColor(_);
10242
chart.id = function(_) {
10243
if (!arguments.length) return id;
10248
//============================================================
10253
nv.models.pie = function() {
10255
//============================================================
10256
// Public Variables with Default Settings
10257
//------------------------------------------------------------
10259
var margin = {top: 0, right: 0, bottom: 0, left: 0}
10262
, getX = function(d) { return d.x }
10263
, getY = function(d) { return d.y }
10264
, getDescription = function(d) { return d.description }
10265
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10266
, color = nv.utils.defaultColor()
10267
, valueFormat = d3.format(',.2f')
10268
, showLabels = true
10269
, pieLabelsOutside = true
10270
, donutLabelsOutside = false
10271
, labelType = "key"
10272
, labelThreshold = .02 //if slice percentage is under this, don't show label
10274
, labelSunbeamLayout = false
10275
, startAngle = false
10278
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
10281
//============================================================
10284
function chart(selection) {
10285
selection.each(function(data) {
10286
var availableWidth = width - margin.left - margin.right,
10287
availableHeight = height - margin.top - margin.bottom,
10288
radius = Math.min(availableWidth, availableHeight) / 2,
10289
arcRadius = radius-(radius / 5),
10290
container = d3.select(this);
10293
//------------------------------------------------------------
10294
// Setup containers and skeleton of chart
10296
//var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]);
10297
var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10298
var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10299
var gEnter = wrapEnter.append('g');
10300
var g = wrap.select('g');
10302
gEnter.append('g').attr('class', 'nv-pie');
10303
gEnter.append('g').attr('class', 'nv-pieLabels');
10305
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10306
g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10307
g.select('.nv-pieLabels').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10309
//------------------------------------------------------------
10313
.on('click', function(d,i) {
10314
dispatch.chartClick({
10323
var arc = d3.svg.arc()
10324
.outerRadius(arcRadius);
10326
if (startAngle) arc.startAngle(startAngle)
10327
if (endAngle) arc.endAngle(endAngle);
10328
if (donut) arc.innerRadius(radius * donutRatio);
10330
// Setup the Pie chart and choose the data element
10331
var pie = d3.layout.pie()
10333
.value(function(d) { return d.disabled ? 0 : getY(d) });
10335
var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
10338
var pieLabels = wrap.select('.nv-pieLabels').selectAll('.nv-label')
10341
slices.exit().remove();
10342
pieLabels.exit().remove();
10344
var ae = slices.enter().append('g')
10345
.attr('class', 'nv-slice')
10346
.on('mouseover', function(d,i){
10347
d3.select(this).classed('hover', true);
10348
dispatch.elementMouseover({
10349
label: getX(d.data),
10350
value: getY(d.data),
10353
pos: [d3.event.pageX, d3.event.pageY],
10357
.on('mouseout', function(d,i){
10358
d3.select(this).classed('hover', false);
10359
dispatch.elementMouseout({
10360
label: getX(d.data),
10361
value: getY(d.data),
10367
.on('click', function(d,i) {
10368
dispatch.elementClick({
10369
label: getX(d.data),
10370
value: getY(d.data),
10376
d3.event.stopPropagation();
10378
.on('dblclick', function(d,i) {
10379
dispatch.elementDblClick({
10380
label: getX(d.data),
10381
value: getY(d.data),
10387
d3.event.stopPropagation();
10391
.attr('fill', function(d,i) { return color(d, i); })
10392
.attr('stroke', function(d,i) { return color(d, i); });
10394
var paths = ae.append('path')
10395
.each(function(d) { this._current = d; });
10398
slices.select('path')
10401
.attrTween('d', arcTween);
10404
// This does the normal label
10405
var labelsArc = d3.svg.arc().innerRadius(0);
10407
if (pieLabelsOutside){ labelsArc = arc; }
10409
if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); }
10411
pieLabels.enter().append("g").classed("nv-label",true)
10412
.each(function(d,i) {
10413
var group = d3.select(this);
10416
.attr('transform', function(d) {
10417
if (labelSunbeamLayout) {
10418
d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10419
d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10420
var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10421
if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10426
return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10428
d.outerRadius = radius + 10; // Set Outer Coordinate
10429
d.innerRadius = radius + 15; // Set Inner Coordinate
10430
return 'translate(' + labelsArc.centroid(d) + ')'
10434
group.append('rect')
10435
.style('stroke', '#fff')
10436
.style('fill', '#fff')
10440
group.append('text')
10441
.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
10442
.style('fill', '#000')
10446
var labelLocationHash = {};
10447
var avgHeight = 14;
10448
var avgWidth = 140;
10449
var createHashKey = function(coordinates) {
10451
return Math.floor(coordinates[0]/avgWidth) * avgWidth + ',' + Math.floor(coordinates[1]/avgHeight) * avgHeight;
10453
pieLabels.transition()
10454
.attr('transform', function(d) {
10455
if (labelSunbeamLayout) {
10456
d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10457
d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10458
var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10459
if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10464
return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10466
d.outerRadius = radius + 10; // Set Outer Coordinate
10467
d.innerRadius = radius + 15; // Set Inner Coordinate
10470
Overlapping pie labels are not good. What this attempts to do is, prevent overlapping.
10471
Each label location is hashed, and if a hash collision occurs, we assume an overlap.
10472
Adjust the label's y-position to remove the overlap.
10474
var center = labelsArc.centroid(d);
10475
var hashKey = createHashKey(center);
10476
if (labelLocationHash[hashKey]) {
10477
center[1] -= avgHeight;
10479
labelLocationHash[createHashKey(center)] = true;
10480
return 'translate(' + center + ')'
10483
pieLabels.select(".nv-label text")
10484
.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
10485
.text(function(d, i) {
10486
var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10488
"key" : getX(d.data),
10489
"value": getY(d.data),
10490
"percent": d3.format('%')(percent)
10492
return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
10497
// Computes the angle of an arc, converting from radians to degrees.
10498
function angle(d) {
10499
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
10500
return a > 90 ? a - 180 : a;
10503
function arcTween(a) {
10504
a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
10505
a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
10506
if (!donut) a.innerRadius = 0;
10507
var i = d3.interpolate(this._current, a);
10508
this._current = i(0);
10509
return function(t) {
10514
function tweenPie(b) {
10516
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
10517
return function(t) {
10528
//============================================================
10529
// Expose Public Variables
10530
//------------------------------------------------------------
10532
chart.dispatch = dispatch;
10533
chart.options = nv.utils.optionsFunc.bind(chart);
10535
chart.margin = function(_) {
10536
if (!arguments.length) return margin;
10537
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10538
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10539
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10540
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10544
chart.width = function(_) {
10545
if (!arguments.length) return width;
10550
chart.height = function(_) {
10551
if (!arguments.length) return height;
10556
chart.values = function(_) {
10557
nv.log("pie.values() is no longer supported.");
10561
chart.x = function(_) {
10562
if (!arguments.length) return getX;
10567
chart.y = function(_) {
10568
if (!arguments.length) return getY;
10569
getY = d3.functor(_);
10573
chart.description = function(_) {
10574
if (!arguments.length) return getDescription;
10575
getDescription = _;
10579
chart.showLabels = function(_) {
10580
if (!arguments.length) return showLabels;
10585
chart.labelSunbeamLayout = function(_) {
10586
if (!arguments.length) return labelSunbeamLayout;
10587
labelSunbeamLayout = _;
10591
chart.donutLabelsOutside = function(_) {
10592
if (!arguments.length) return donutLabelsOutside;
10593
donutLabelsOutside = _;
10597
chart.pieLabelsOutside = function(_) {
10598
if (!arguments.length) return pieLabelsOutside;
10599
pieLabelsOutside = _;
10603
chart.labelType = function(_) {
10604
if (!arguments.length) return labelType;
10606
labelType = labelType || "key";
10610
chart.donut = function(_) {
10611
if (!arguments.length) return donut;
10616
chart.donutRatio = function(_) {
10617
if (!arguments.length) return donutRatio;
10622
chart.startAngle = function(_) {
10623
if (!arguments.length) return startAngle;
10628
chart.endAngle = function(_) {
10629
if (!arguments.length) return endAngle;
10634
chart.id = function(_) {
10635
if (!arguments.length) return id;
10640
chart.color = function(_) {
10641
if (!arguments.length) return color;
10642
color = nv.utils.getColor(_);
10646
chart.valueFormat = function(_) {
10647
if (!arguments.length) return valueFormat;
10652
chart.labelThreshold = function(_) {
10653
if (!arguments.length) return labelThreshold;
10654
labelThreshold = _;
10657
//============================================================
10662
nv.models.pieChart = function() {
10664
//============================================================
10665
// Public Variables with Default Settings
10666
//------------------------------------------------------------
10668
var pie = nv.models.pie()
10669
, legend = nv.models.legend()
10672
var margin = {top: 30, right: 20, bottom: 20, left: 20}
10675
, showLegend = true
10676
, color = nv.utils.defaultColor()
10678
, tooltip = function(key, y, e, graph) {
10679
return '<h3>' + key + '</h3>' +
10683
, defaultState = null
10684
, noData = "No Data Available."
10685
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
10688
//============================================================
10691
//============================================================
10692
// Private Variables
10693
//------------------------------------------------------------
10695
var showTooltip = function(e, offsetElement) {
10696
var tooltipLabel = pie.description()(e.point) || pie.x()(e.point)
10697
var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
10698
top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
10699
y = pie.valueFormat()(pie.y()(e.point)),
10700
content = tooltip(tooltipLabel, y, e, chart);
10702
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
10705
//============================================================
10708
function chart(selection) {
10709
selection.each(function(data) {
10710
var container = d3.select(this),
10713
var availableWidth = (width || parseInt(container.style('width')) || 960)
10714
- margin.left - margin.right,
10715
availableHeight = (height || parseInt(container.style('height')) || 400)
10716
- margin.top - margin.bottom;
10718
chart.update = function() { container.transition().call(chart); };
10719
chart.container = this;
10721
//set state.disabled
10722
state.disabled = data.map(function(d) { return !!d.disabled });
10724
if (!defaultState) {
10727
for (key in state) {
10728
if (state[key] instanceof Array)
10729
defaultState[key] = state[key].slice(0);
10731
defaultState[key] = state[key];
10735
//------------------------------------------------------------
10736
// Display No Data message if there's nothing to show.
10738
if (!data || !data.length) {
10739
var noDataText = container.selectAll('.nv-noData').data([noData]);
10741
noDataText.enter().append('text')
10742
.attr('class', 'nvd3 nv-noData')
10743
.attr('dy', '-.7em')
10744
.style('text-anchor', 'middle');
10747
.attr('x', margin.left + availableWidth / 2)
10748
.attr('y', margin.top + availableHeight / 2)
10749
.text(function(d) { return d });
10753
container.selectAll('.nv-noData').remove();
10756
//------------------------------------------------------------
10759
//------------------------------------------------------------
10760
// Setup containers and skeleton of chart
10762
var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
10763
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
10764
var g = wrap.select('g');
10766
gEnter.append('g').attr('class', 'nv-pieWrap');
10767
gEnter.append('g').attr('class', 'nv-legendWrap');
10769
//------------------------------------------------------------
10772
//------------------------------------------------------------
10777
.width( availableWidth )
10780
wrap.select('.nv-legendWrap')
10784
if ( margin.top != legend.height()) {
10785
margin.top = legend.height();
10786
availableHeight = (height || parseInt(container.style('height')) || 400)
10787
- margin.top - margin.bottom;
10790
wrap.select('.nv-legendWrap')
10791
.attr('transform', 'translate(0,' + (-margin.top) +')');
10794
//------------------------------------------------------------
10797
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10800
//------------------------------------------------------------
10801
// Main Chart Component(s)
10804
.width(availableWidth)
10805
.height(availableHeight);
10808
var pieWrap = g.select('.nv-pieWrap')
10811
d3.transition(pieWrap).call(pie);
10813
//------------------------------------------------------------
10816
//============================================================
10817
// Event Handling/Dispatching (in chart's scope)
10818
//------------------------------------------------------------
10820
legend.dispatch.on('stateChange', function(newState) {
10822
dispatch.stateChange(state);
10826
pie.dispatch.on('elementMouseout.tooltip', function(e) {
10827
dispatch.tooltipHide(e);
10830
// Update chart from a state object passed to event handler
10831
dispatch.on('changeState', function(e) {
10833
if (typeof e.disabled !== 'undefined') {
10834
data.forEach(function(series,i) {
10835
series.disabled = e.disabled[i];
10838
state.disabled = e.disabled;
10844
//============================================================
10852
//============================================================
10853
// Event Handling/Dispatching (out of chart's scope)
10854
//------------------------------------------------------------
10856
pie.dispatch.on('elementMouseover.tooltip', function(e) {
10857
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10858
dispatch.tooltipShow(e);
10861
dispatch.on('tooltipShow', function(e) {
10862
if (tooltips) showTooltip(e);
10865
dispatch.on('tooltipHide', function() {
10866
if (tooltips) nv.tooltip.cleanup();
10869
//============================================================
10872
//============================================================
10873
// Expose Public Variables
10874
//------------------------------------------------------------
10876
// expose chart's sub-components
10877
chart.legend = legend;
10878
chart.dispatch = dispatch;
10881
d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'labelType', 'donut', 'donutRatio', 'labelThreshold');
10882
chart.options = nv.utils.optionsFunc.bind(chart);
10884
chart.margin = function(_) {
10885
if (!arguments.length) return margin;
10886
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10887
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10888
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10889
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10893
chart.width = function(_) {
10894
if (!arguments.length) return width;
10899
chart.height = function(_) {
10900
if (!arguments.length) return height;
10905
chart.color = function(_) {
10906
if (!arguments.length) return color;
10907
color = nv.utils.getColor(_);
10908
legend.color(color);
10913
chart.showLegend = function(_) {
10914
if (!arguments.length) return showLegend;
10919
chart.tooltips = function(_) {
10920
if (!arguments.length) return tooltips;
10925
chart.tooltipContent = function(_) {
10926
if (!arguments.length) return tooltip;
10931
chart.state = function(_) {
10932
if (!arguments.length) return state;
10937
chart.defaultState = function(_) {
10938
if (!arguments.length) return defaultState;
10943
chart.noData = function(_) {
10944
if (!arguments.length) return noData;
10949
//============================================================
10955
nv.models.scatter = function() {
10957
//============================================================
10958
// Public Variables with Default Settings
10959
//------------------------------------------------------------
10961
var margin = {top: 0, right: 0, bottom: 0, left: 0}
10964
, color = nv.utils.defaultColor() // chooses color
10965
, id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10966
, x = d3.scale.linear()
10967
, y = d3.scale.linear()
10968
, z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
10969
, getX = function(d) { return d.x } // accessor to get the x value
10970
, getY = function(d) { return d.y } // accessor to get the y value
10971
, getSize = function(d) { return d.size || 1} // accessor to get the point size
10972
, getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
10973
, onlyCircles = true // Set to false to use shapes
10974
, forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
10975
, forceY = [] // List of numbers to Force into the Y scale
10976
, forceSize = [] // List of numbers to Force into the Size scale
10977
, interactive = true // If true, plots a voronoi overlay for advanced point intersection
10979
, pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
10980
, padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10981
, padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
10982
, clipEdge = false // if true, masks points within x and y scale
10983
, clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
10984
, clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
10985
, xDomain = null // Override x domain (skips the calculation from data)
10986
, yDomain = null // Override y domain
10987
, xRange = null // Override x range
10988
, yRange = null // Override y range
10989
, sizeDomain = null // Override point size domain
10991
, singlePoint = false
10992
, dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
10993
, useVoronoi = true
10996
//============================================================
10999
//============================================================
11000
// Private Variables
11001
//------------------------------------------------------------
11003
var x0, y0, z0 // used to store previous scales
11005
, needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
11008
//============================================================
11011
function chart(selection) {
11012
selection.each(function(data) {
11013
var availableWidth = width - margin.left - margin.right,
11014
availableHeight = height - margin.top - margin.bottom,
11015
container = d3.select(this);
11017
//add series index to each data point for reference
11018
data.forEach(function(series, i) {
11019
series.values.forEach(function(point) {
11024
//------------------------------------------------------------
11027
// remap and flatten the data for use in calculating the scales' domains
11028
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
11030
data.map(function(d) {
11031
return d.values.map(function(d,i) {
11032
return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
11037
x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
11039
if (padData && data[0])
11040
x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
11041
//x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
11043
x.range(xRange || [0, availableWidth]);
11045
y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
11046
.range(yRange || [availableHeight, 0]);
11048
z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
11049
.range(sizeRange || [16, 256]);
11051
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
11052
if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
11053
if (x.domain()[0] === x.domain()[1])
11055
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
11056
: x.domain([-1,1]);
11058
if (y.domain()[0] === y.domain()[1])
11060
y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
11061
: y.domain([-1,1]);
11063
if ( isNaN(x.domain()[0])) {
11067
if ( isNaN(y.domain()[0])) {
11076
//------------------------------------------------------------
11079
//------------------------------------------------------------
11080
// Setup containers and skeleton of chart
11082
var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
11083
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
11084
var defsEnter = wrapEnter.append('defs');
11085
var gEnter = wrapEnter.append('g');
11086
var g = wrap.select('g');
11088
gEnter.append('g').attr('class', 'nv-groups');
11089
gEnter.append('g').attr('class', 'nv-point-paths');
11091
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11093
//------------------------------------------------------------
11096
defsEnter.append('clipPath')
11097
.attr('id', 'nv-edge-clip-' + id)
11100
wrap.select('#nv-edge-clip-' + id + ' rect')
11101
.attr('width', availableWidth)
11102
.attr('height', (availableHeight > 0) ? availableHeight : 0);
11104
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
11107
function updateInteractiveLayer() {
11109
if (!interactive) return false;
11113
var vertices = d3.merge(data.map(function(group, groupIndex) {
11114
return group.values
11115
.map(function(point, pointIndex) {
11116
// *Adding noise to make duplicates very unlikely
11117
// *Injecting series and point index for reference
11118
/* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
11120
var pX = getX(point,pointIndex);
11121
var pY = getY(point,pointIndex);
11123
return [x(pX)+ Math.random() * 1e-7,
11124
y(pY)+ Math.random() * 1e-7,
11126
pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
11128
.filter(function(pointArray, pointIndex) {
11129
return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
11136
//inject series and point index for reference into voronoi
11137
if (useVoronoi === true) {
11140
var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
11144
pointClipsEnter.append('clipPath')
11145
.attr('class', 'nv-point-clips')
11146
.attr('id', 'nv-points-clip-' + id);
11148
var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
11150
pointClips.enter().append('circle')
11151
.attr('r', clipRadius);
11152
pointClips.exit().remove();
11154
.attr('cx', function(d) { return d[0] })
11155
.attr('cy', function(d) { return d[1] });
11157
wrap.select('.nv-point-paths')
11158
.attr('clip-path', 'url(#nv-points-clip-' + id + ')');
11162
if(vertices.length) {
11163
// Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
11164
vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
11165
vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11166
vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11167
vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11170
var bounds = d3.geom.polygon([
11173
[width + 10,height + 10],
11177
var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11179
'data': bounds.clip(d),
11180
'series': vertices[i][2],
11181
'point': vertices[i][3]
11186
var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
11188
pointPaths.enter().append('path')
11189
.attr('class', function(d,i) { return 'nv-path-'+i; });
11190
pointPaths.exit().remove();
11192
.attr('d', function(d) {
11193
if (d.data.length === 0)
11196
return 'M' + d.data.join('L') + 'Z';
11199
var mouseEventCallback = function(d,mDispatch) {
11200
if (needsUpdate) return 0;
11201
var series = data[d.series];
11202
if (typeof series === 'undefined') return;
11204
var point = series.values[d.point];
11209
pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
11210
seriesIndex: d.series,
11211
pointIndex: d.point
11216
.on('click', function(d) {
11217
mouseEventCallback(d, dispatch.elementClick);
11219
.on('mouseover', function(d) {
11220
mouseEventCallback(d, dispatch.elementMouseover);
11222
.on('mouseout', function(d, i) {
11223
mouseEventCallback(d, dispatch.elementMouseout);
11229
// bring data in form needed for click handlers
11230
var dataWithPoints = vertices.map(function(d, i) {
11233
'series': vertices[i][2],
11234
'point': vertices[i][3]
11239
// add event handlers to points instead voronoi paths
11240
wrap.select('.nv-groups').selectAll('.nv-group')
11241
.selectAll('.nv-point')
11242
//.data(dataWithPoints)
11243
//.style('pointer-events', 'auto') // recativate events, disabled by css
11244
.on('click', function(d,i) {
11245
//nv.log('test', d, i);
11246
if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11247
var series = data[d.series],
11248
point = series.values[i];
11250
dispatch.elementClick({
11253
pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11254
seriesIndex: d.series,
11258
.on('mouseover', function(d,i) {
11259
if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11260
var series = data[d.series],
11261
point = series.values[i];
11263
dispatch.elementMouseover({
11266
pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11267
seriesIndex: d.series,
11271
.on('mouseout', function(d,i) {
11272
if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11273
var series = data[d.series],
11274
point = series.values[i];
11276
dispatch.elementMouseout({
11279
seriesIndex: d.series,
11285
needsUpdate = false;
11288
needsUpdate = true;
11290
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11291
.data(function(d) { return d }, function(d) { return d.key });
11292
groups.enter().append('g')
11293
.style('stroke-opacity', 1e-6)
11294
.style('fill-opacity', 1e-6);
11298
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11299
.classed('hover', function(d) { return d.hover });
11302
.style('fill', function(d,i) { return color(d, i) })
11303
.style('stroke', function(d,i) { return color(d, i) })
11304
.style('stroke-opacity', 1)
11305
.style('fill-opacity', .5);
11310
var points = groups.selectAll('circle.nv-point')
11311
.data(function(d) { return d.values }, pointKey);
11312
points.enter().append('circle')
11313
.style('fill', function (d,i) { return d.color })
11314
.style('stroke', function (d,i) { return d.color })
11315
.attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
11316
.attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
11317
.attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11318
points.exit().remove();
11319
groups.exit().selectAll('path.nv-point').transition()
11320
.attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11321
.attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11323
points.each(function(d,i) {
11325
.classed('nv-point', true)
11326
.classed('nv-point-' + i, true)
11327
.classed('hover',false)
11330
points.transition()
11331
.attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11332
.attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11333
.attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11337
var points = groups.selectAll('path.nv-point')
11338
.data(function(d) { return d.values });
11339
points.enter().append('path')
11340
.style('fill', function (d,i) { return d.color })
11341
.style('stroke', function (d,i) { return d.color })
11342
.attr('transform', function(d,i) {
11343
return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
11348
.size(function(d,i) { return z(getSize(d,i)) })
11350
points.exit().remove();
11351
groups.exit().selectAll('path.nv-point')
11353
.attr('transform', function(d,i) {
11354
return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11357
points.each(function(d,i) {
11359
.classed('nv-point', true)
11360
.classed('nv-point-' + i, true)
11361
.classed('hover',false)
11364
points.transition()
11365
.attr('transform', function(d,i) {
11366
//nv.log(d,i,getX(d,i), x(getX(d,i)));
11367
return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11372
.size(function(d,i) { return z(getSize(d,i)) })
11377
// Delay updating the invisible interactive layer for smoother animation
11378
clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11379
timeoutID = setTimeout(updateInteractiveLayer, 300);
11380
//updateInteractiveLayer();
11382
//store old scales for use in transitions on update
11393
//============================================================
11394
// Event Handling/Dispatching (out of chart's scope)
11395
//------------------------------------------------------------
11396
chart.clearHighlights = function() {
11397
//Remove the 'hover' class from all highlighted points.
11398
d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false);
11401
chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) {
11402
d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11403
.classed("hover",isHoverOver);
11407
dispatch.on('elementMouseover.point', function(d) {
11408
if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true);
11411
dispatch.on('elementMouseout.point', function(d) {
11412
if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false);
11415
//============================================================
11418
//============================================================
11419
// Expose Public Variables
11420
//------------------------------------------------------------
11422
chart.dispatch = dispatch;
11423
chart.options = nv.utils.optionsFunc.bind(chart);
11425
chart.x = function(_) {
11426
if (!arguments.length) return getX;
11427
getX = d3.functor(_);
11431
chart.y = function(_) {
11432
if (!arguments.length) return getY;
11433
getY = d3.functor(_);
11437
chart.size = function(_) {
11438
if (!arguments.length) return getSize;
11439
getSize = d3.functor(_);
11443
chart.margin = function(_) {
11444
if (!arguments.length) return margin;
11445
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11446
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11447
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11448
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11452
chart.width = function(_) {
11453
if (!arguments.length) return width;
11458
chart.height = function(_) {
11459
if (!arguments.length) return height;
11464
chart.xScale = function(_) {
11465
if (!arguments.length) return x;
11470
chart.yScale = function(_) {
11471
if (!arguments.length) return y;
11476
chart.zScale = function(_) {
11477
if (!arguments.length) return z;
11482
chart.xDomain = function(_) {
11483
if (!arguments.length) return xDomain;
11488
chart.yDomain = function(_) {
11489
if (!arguments.length) return yDomain;
11494
chart.sizeDomain = function(_) {
11495
if (!arguments.length) return sizeDomain;
11500
chart.xRange = function(_) {
11501
if (!arguments.length) return xRange;
11506
chart.yRange = function(_) {
11507
if (!arguments.length) return yRange;
11512
chart.sizeRange = function(_) {
11513
if (!arguments.length) return sizeRange;
11518
chart.forceX = function(_) {
11519
if (!arguments.length) return forceX;
11524
chart.forceY = function(_) {
11525
if (!arguments.length) return forceY;
11530
chart.forceSize = function(_) {
11531
if (!arguments.length) return forceSize;
11536
chart.interactive = function(_) {
11537
if (!arguments.length) return interactive;
11542
chart.pointKey = function(_) {
11543
if (!arguments.length) return pointKey;
11548
chart.pointActive = function(_) {
11549
if (!arguments.length) return pointActive;
11554
chart.padData = function(_) {
11555
if (!arguments.length) return padData;
11560
chart.padDataOuter = function(_) {
11561
if (!arguments.length) return padDataOuter;
11566
chart.clipEdge = function(_) {
11567
if (!arguments.length) return clipEdge;
11572
chart.clipVoronoi= function(_) {
11573
if (!arguments.length) return clipVoronoi;
11578
chart.useVoronoi= function(_) {
11579
if (!arguments.length) return useVoronoi;
11581
if (useVoronoi === false) {
11582
clipVoronoi = false;
11587
chart.clipRadius = function(_) {
11588
if (!arguments.length) return clipRadius;
11593
chart.color = function(_) {
11594
if (!arguments.length) return color;
11595
color = nv.utils.getColor(_);
11599
chart.shape = function(_) {
11600
if (!arguments.length) return getShape;
11605
chart.onlyCircles = function(_) {
11606
if (!arguments.length) return onlyCircles;
11611
chart.id = function(_) {
11612
if (!arguments.length) return id;
11617
chart.singlePoint = function(_) {
11618
if (!arguments.length) return singlePoint;
11623
//============================================================
11628
nv.models.scatterChart = function() {
11630
//============================================================
11631
// Public Variables with Default Settings
11632
//------------------------------------------------------------
11634
var scatter = nv.models.scatter()
11635
, xAxis = nv.models.axis()
11636
, yAxis = nv.models.axis()
11637
, legend = nv.models.legend()
11638
, controls = nv.models.legend()
11639
, distX = nv.models.distribution()
11640
, distY = nv.models.distribution()
11643
var margin = {top: 30, right: 20, bottom: 50, left: 75}
11646
, color = nv.utils.defaultColor()
11647
, x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
11648
, y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
11651
, showDistX = false
11652
, showDistY = false
11653
, showLegend = true
11656
, rightAlignYAxis = false
11657
, showControls = !!d3.fisheye
11659
, pauseFisheye = false
11661
, tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
11662
, tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
11665
, defaultState = null
11666
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
11667
, noData = "No Data Available."
11668
, transitionDuration = 250
11680
.orient((rightAlignYAxis) ? 'right' : 'left')
11690
controls.updateState(false);
11692
//============================================================
11695
//============================================================
11696
// Private Variables
11697
//------------------------------------------------------------
11701
var showTooltip = function(e, offsetElement) {
11702
//TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
11704
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11705
top = e.pos[1] + ( offsetElement.offsetTop || 0),
11706
leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11707
topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
11708
leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
11709
topY = e.pos[1] + ( offsetElement.offsetTop || 0),
11710
xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
11711
yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
11713
if( tooltipX != null )
11714
nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
11715
if( tooltipY != null )
11716
nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
11717
if( tooltip != null )
11718
nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
11721
var controlsData = [
11722
{ key: 'Magnify', disabled: true }
11725
//============================================================
11728
function chart(selection) {
11729
selection.each(function(data) {
11730
var container = d3.select(this),
11733
var availableWidth = (width || parseInt(container.style('width')) || 960)
11734
- margin.left - margin.right,
11735
availableHeight = (height || parseInt(container.style('height')) || 400)
11736
- margin.top - margin.bottom;
11738
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
11739
chart.container = this;
11741
//set state.disabled
11742
state.disabled = data.map(function(d) { return !!d.disabled });
11744
if (!defaultState) {
11747
for (key in state) {
11748
if (state[key] instanceof Array)
11749
defaultState[key] = state[key].slice(0);
11751
defaultState[key] = state[key];
11755
//------------------------------------------------------------
11756
// Display noData message if there's nothing to show.
11758
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11759
var noDataText = container.selectAll('.nv-noData').data([noData]);
11761
noDataText.enter().append('text')
11762
.attr('class', 'nvd3 nv-noData')
11763
.attr('dy', '-.7em')
11764
.style('text-anchor', 'middle');
11767
.attr('x', margin.left + availableWidth / 2)
11768
.attr('y', margin.top + availableHeight / 2)
11769
.text(function(d) { return d });
11773
container.selectAll('.nv-noData').remove();
11776
//------------------------------------------------------------
11779
//------------------------------------------------------------
11785
//------------------------------------------------------------
11788
//------------------------------------------------------------
11789
// Setup containers and skeleton of chart
11791
var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11792
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11793
var gEnter = wrapEnter.append('g');
11794
var g = wrap.select('g');
11796
// background for pointer events
11797
gEnter.append('rect').attr('class', 'nvd3 nv-background');
11799
gEnter.append('g').attr('class', 'nv-x nv-axis');
11800
gEnter.append('g').attr('class', 'nv-y nv-axis');
11801
gEnter.append('g').attr('class', 'nv-scatterWrap');
11802
gEnter.append('g').attr('class', 'nv-distWrap');
11803
gEnter.append('g').attr('class', 'nv-legendWrap');
11804
gEnter.append('g').attr('class', 'nv-controlsWrap');
11806
//------------------------------------------------------------
11809
//------------------------------------------------------------
11813
var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
11814
legend.width(legendWidth);
11816
wrap.select('.nv-legendWrap')
11820
if ( margin.top != legend.height()) {
11821
margin.top = legend.height();
11822
availableHeight = (height || parseInt(container.style('height')) || 400)
11823
- margin.top - margin.bottom;
11826
wrap.select('.nv-legendWrap')
11827
.attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
11830
//------------------------------------------------------------
11833
//------------------------------------------------------------
11836
if (showControls) {
11837
controls.width(180).color(['#444']);
11838
g.select('.nv-controlsWrap')
11839
.datum(controlsData)
11840
.attr('transform', 'translate(0,' + (-margin.top) +')')
11844
//------------------------------------------------------------
11847
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11849
if (rightAlignYAxis) {
11850
g.select(".nv-y.nv-axis")
11851
.attr("transform", "translate(" + availableWidth + ",0)");
11854
//------------------------------------------------------------
11855
// Main Chart Component(s)
11858
.width(availableWidth)
11859
.height(availableHeight)
11860
.color(data.map(function(d,i) {
11861
return d.color || color(d, i);
11862
}).filter(function(d,i) { return !data[i].disabled }));
11864
if (xPadding !== 0)
11865
scatter.xDomain(null);
11867
if (yPadding !== 0)
11868
scatter.yDomain(null);
11870
wrap.select('.nv-scatterWrap')
11871
.datum(data.filter(function(d) { return !d.disabled }))
11874
//Adjust for x and y padding
11875
if (xPadding !== 0) {
11876
var xRange = x.domain()[1] - x.domain()[0];
11877
scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
11880
if (yPadding !== 0) {
11881
var yRange = y.domain()[1] - y.domain()[0];
11882
scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
11885
//Only need to update the scatter again if x/yPadding changed the domain.
11886
if (yPadding !== 0 || xPadding !== 0) {
11887
wrap.select('.nv-scatterWrap')
11888
.datum(data.filter(function(d) { return !d.disabled }))
11892
//------------------------------------------------------------
11895
//------------------------------------------------------------
11900
.ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
11901
.tickSize( -availableHeight , 0);
11903
g.select('.nv-x.nv-axis')
11904
.attr('transform', 'translate(0,' + y.range()[0] + ')')
11912
.ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
11913
.tickSize( -availableWidth, 0);
11915
g.select('.nv-y.nv-axis')
11922
.getData(scatter.x())
11924
.width(availableWidth)
11925
.color(data.map(function(d,i) {
11926
return d.color || color(d, i);
11927
}).filter(function(d,i) { return !data[i].disabled }));
11928
gEnter.select('.nv-distWrap').append('g')
11929
.attr('class', 'nv-distributionX');
11930
g.select('.nv-distributionX')
11931
.attr('transform', 'translate(0,' + y.range()[0] + ')')
11932
.datum(data.filter(function(d) { return !d.disabled }))
11938
.getData(scatter.y())
11940
.width(availableHeight)
11941
.color(data.map(function(d,i) {
11942
return d.color || color(d, i);
11943
}).filter(function(d,i) { return !data[i].disabled }));
11944
gEnter.select('.nv-distWrap').append('g')
11945
.attr('class', 'nv-distributionY');
11946
g.select('.nv-distributionY')
11948
'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
11949
.datum(data.filter(function(d) { return !d.disabled }))
11953
//------------------------------------------------------------
11959
g.select('.nv-background')
11960
.attr('width', availableWidth)
11961
.attr('height', availableHeight);
11963
g.select('.nv-background').on('mousemove', updateFisheye);
11964
g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
11965
scatter.dispatch.on('elementClick.freezeFisheye', function() {
11966
pauseFisheye = !pauseFisheye;
11971
function updateFisheye() {
11972
if (pauseFisheye) {
11973
g.select('.nv-point-paths').style('pointer-events', 'all');
11977
g.select('.nv-point-paths').style('pointer-events', 'none' );
11979
var mouse = d3.mouse(this);
11980
x.distortion(fisheye).focus(mouse[0]);
11981
y.distortion(fisheye).focus(mouse[1]);
11983
g.select('.nv-scatterWrap')
11987
g.select('.nv-x.nv-axis').call(xAxis);
11990
g.select('.nv-y.nv-axis').call(yAxis);
11992
g.select('.nv-distributionX')
11993
.datum(data.filter(function(d) { return !d.disabled }))
11995
g.select('.nv-distributionY')
11996
.datum(data.filter(function(d) { return !d.disabled }))
12002
//============================================================
12003
// Event Handling/Dispatching (in chart's scope)
12004
//------------------------------------------------------------
12006
controls.dispatch.on('legendClick', function(d,i) {
12007
d.disabled = !d.disabled;
12009
fisheye = d.disabled ? 0 : 2.5;
12010
g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12011
g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12014
x.distortion(fisheye).focus(0);
12015
y.distortion(fisheye).focus(0);
12017
g.select('.nv-scatterWrap').call(scatter);
12018
g.select('.nv-x.nv-axis').call(xAxis);
12019
g.select('.nv-y.nv-axis').call(yAxis);
12021
pauseFisheye = false;
12027
legend.dispatch.on('stateChange', function(newState) {
12028
state.disabled = newState.disabled;
12029
dispatch.stateChange(state);
12033
scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12034
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12035
.attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
12036
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12037
.attr('x2', e.pos[0] + distX.size());
12039
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12040
dispatch.tooltipShow(e);
12043
dispatch.on('tooltipShow', function(e) {
12044
if (tooltips) showTooltip(e, that.parentNode);
12047
// Update chart from a state object passed to event handler
12048
dispatch.on('changeState', function(e) {
12050
if (typeof e.disabled !== 'undefined') {
12051
data.forEach(function(series,i) {
12052
series.disabled = e.disabled[i];
12055
state.disabled = e.disabled;
12061
//============================================================
12064
//store old scales for use in transitions on update
12075
//============================================================
12076
// Event Handling/Dispatching (out of chart's scope)
12077
//------------------------------------------------------------
12079
scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12080
dispatch.tooltipHide(e);
12082
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12084
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12085
.attr('x2', distY.size());
12087
dispatch.on('tooltipHide', function() {
12088
if (tooltips) nv.tooltip.cleanup();
12091
//============================================================
12094
//============================================================
12095
// Expose Public Variables
12096
//------------------------------------------------------------
12098
// expose chart's sub-components
12099
chart.dispatch = dispatch;
12100
chart.scatter = scatter;
12101
chart.legend = legend;
12102
chart.controls = controls;
12103
chart.xAxis = xAxis;
12104
chart.yAxis = yAxis;
12105
chart.distX = distX;
12106
chart.distY = distY;
12108
d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
12109
chart.options = nv.utils.optionsFunc.bind(chart);
12111
chart.margin = function(_) {
12112
if (!arguments.length) return margin;
12113
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12114
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12115
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12116
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12120
chart.width = function(_) {
12121
if (!arguments.length) return width;
12126
chart.height = function(_) {
12127
if (!arguments.length) return height;
12132
chart.color = function(_) {
12133
if (!arguments.length) return color;
12134
color = nv.utils.getColor(_);
12135
legend.color(color);
12136
distX.color(color);
12137
distY.color(color);
12141
chart.showDistX = function(_) {
12142
if (!arguments.length) return showDistX;
12147
chart.showDistY = function(_) {
12148
if (!arguments.length) return showDistY;
12153
chart.showControls = function(_) {
12154
if (!arguments.length) return showControls;
12159
chart.showLegend = function(_) {
12160
if (!arguments.length) return showLegend;
12165
chart.showXAxis = function(_) {
12166
if (!arguments.length) return showXAxis;
12171
chart.showYAxis = function(_) {
12172
if (!arguments.length) return showYAxis;
12177
chart.rightAlignYAxis = function(_) {
12178
if(!arguments.length) return rightAlignYAxis;
12179
rightAlignYAxis = _;
12180
yAxis.orient( (_) ? 'right' : 'left');
12185
chart.fisheye = function(_) {
12186
if (!arguments.length) return fisheye;
12191
chart.xPadding = function(_) {
12192
if (!arguments.length) return xPadding;
12197
chart.yPadding = function(_) {
12198
if (!arguments.length) return yPadding;
12203
chart.tooltips = function(_) {
12204
if (!arguments.length) return tooltips;
12209
chart.tooltipContent = function(_) {
12210
if (!arguments.length) return tooltip;
12215
chart.tooltipXContent = function(_) {
12216
if (!arguments.length) return tooltipX;
12221
chart.tooltipYContent = function(_) {
12222
if (!arguments.length) return tooltipY;
12227
chart.state = function(_) {
12228
if (!arguments.length) return state;
12233
chart.defaultState = function(_) {
12234
if (!arguments.length) return defaultState;
12239
chart.noData = function(_) {
12240
if (!arguments.length) return noData;
12245
chart.transitionDuration = function(_) {
12246
if (!arguments.length) return transitionDuration;
12247
transitionDuration = _;
12251
//============================================================
12257
nv.models.scatterPlusLineChart = function() {
12259
//============================================================
12260
// Public Variables with Default Settings
12261
//------------------------------------------------------------
12263
var scatter = nv.models.scatter()
12264
, xAxis = nv.models.axis()
12265
, yAxis = nv.models.axis()
12266
, legend = nv.models.legend()
12267
, controls = nv.models.legend()
12268
, distX = nv.models.distribution()
12269
, distY = nv.models.distribution()
12272
var margin = {top: 30, right: 20, bottom: 50, left: 75}
12275
, color = nv.utils.defaultColor()
12276
, x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12277
, y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12278
, showDistX = false
12279
, showDistY = false
12280
, showLegend = true
12283
, rightAlignYAxis = false
12284
, showControls = !!d3.fisheye
12286
, pauseFisheye = false
12288
, tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
12289
, tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
12290
, tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>'
12291
+ '<p>' + date + '</p>' }
12293
, defaultState = null
12294
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
12295
, noData = "No Data Available."
12296
, transitionDuration = 250
12308
.orient((rightAlignYAxis) ? 'right' : 'left')
12318
controls.updateState(false);
12319
//============================================================
12322
//============================================================
12323
// Private Variables
12324
//------------------------------------------------------------
12328
var showTooltip = function(e, offsetElement) {
12329
//TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12331
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12332
top = e.pos[1] + ( offsetElement.offsetTop || 0),
12333
leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12334
topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12335
leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12336
topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12337
xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12338
yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12340
if( tooltipX != null )
12341
nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12342
if( tooltipY != null )
12343
nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12344
if( tooltip != null )
12345
nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12348
var controlsData = [
12349
{ key: 'Magnify', disabled: true }
12352
//============================================================
12355
function chart(selection) {
12356
selection.each(function(data) {
12357
var container = d3.select(this),
12360
var availableWidth = (width || parseInt(container.style('width')) || 960)
12361
- margin.left - margin.right,
12362
availableHeight = (height || parseInt(container.style('height')) || 400)
12363
- margin.top - margin.bottom;
12365
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
12366
chart.container = this;
12368
//set state.disabled
12369
state.disabled = data.map(function(d) { return !!d.disabled });
12371
if (!defaultState) {
12374
for (key in state) {
12375
if (state[key] instanceof Array)
12376
defaultState[key] = state[key].slice(0);
12378
defaultState[key] = state[key];
12382
//------------------------------------------------------------
12383
// Display noData message if there's nothing to show.
12385
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12386
var noDataText = container.selectAll('.nv-noData').data([noData]);
12388
noDataText.enter().append('text')
12389
.attr('class', 'nvd3 nv-noData')
12390
.attr('dy', '-.7em')
12391
.style('text-anchor', 'middle');
12394
.attr('x', margin.left + availableWidth / 2)
12395
.attr('y', margin.top + availableHeight / 2)
12396
.text(function(d) { return d });
12400
container.selectAll('.nv-noData').remove();
12403
//------------------------------------------------------------
12406
//------------------------------------------------------------
12409
x = scatter.xScale();
12410
y = scatter.yScale();
12415
//------------------------------------------------------------
12418
//------------------------------------------------------------
12419
// Setup containers and skeleton of chart
12421
var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12422
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12423
var gEnter = wrapEnter.append('g');
12424
var g = wrap.select('g')
12426
// background for pointer events
12427
gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
12429
gEnter.append('g').attr('class', 'nv-x nv-axis');
12430
gEnter.append('g').attr('class', 'nv-y nv-axis');
12431
gEnter.append('g').attr('class', 'nv-scatterWrap');
12432
gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
12433
gEnter.append('g').attr('class', 'nv-distWrap');
12434
gEnter.append('g').attr('class', 'nv-legendWrap');
12435
gEnter.append('g').attr('class', 'nv-controlsWrap');
12437
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12439
if (rightAlignYAxis) {
12440
g.select(".nv-y.nv-axis")
12441
.attr("transform", "translate(" + availableWidth + ",0)");
12444
//------------------------------------------------------------
12447
//------------------------------------------------------------
12451
legend.width( availableWidth / 2 );
12453
wrap.select('.nv-legendWrap')
12457
if ( margin.top != legend.height()) {
12458
margin.top = legend.height();
12459
availableHeight = (height || parseInt(container.style('height')) || 400)
12460
- margin.top - margin.bottom;
12463
wrap.select('.nv-legendWrap')
12464
.attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
12467
//------------------------------------------------------------
12470
//------------------------------------------------------------
12473
if (showControls) {
12474
controls.width(180).color(['#444']);
12475
g.select('.nv-controlsWrap')
12476
.datum(controlsData)
12477
.attr('transform', 'translate(0,' + (-margin.top) +')')
12481
//------------------------------------------------------------
12484
//------------------------------------------------------------
12485
// Main Chart Component(s)
12488
.width(availableWidth)
12489
.height(availableHeight)
12490
.color(data.map(function(d,i) {
12491
return d.color || color(d, i);
12492
}).filter(function(d,i) { return !data[i].disabled }))
12494
wrap.select('.nv-scatterWrap')
12495
.datum(data.filter(function(d) { return !d.disabled }))
12498
wrap.select('.nv-regressionLinesWrap')
12499
.attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
12501
var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
12502
.data(function(d) {return d });
12504
regWrap.enter().append('g').attr('class', 'nv-regLines');
12506
var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]});
12507
var regLineEnter = regLine.enter()
12508
.append('line').attr('class', 'nv-regLine')
12509
.style('stroke-opacity', 0);
12513
.attr('x1', x.range()[0])
12514
.attr('x2', x.range()[1])
12515
.attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) })
12516
.attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
12517
.style('stroke', function(d,i,j) { return color(d,j) })
12518
.style('stroke-opacity', function(d,i) {
12519
return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
12522
//------------------------------------------------------------
12525
//------------------------------------------------------------
12531
.ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
12532
.tickSize( -availableHeight , 0);
12534
g.select('.nv-x.nv-axis')
12535
.attr('transform', 'translate(0,' + y.range()[0] + ')')
12542
.ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
12543
.tickSize( -availableWidth, 0);
12545
g.select('.nv-y.nv-axis')
12552
.getData(scatter.x())
12554
.width(availableWidth)
12555
.color(data.map(function(d,i) {
12556
return d.color || color(d, i);
12557
}).filter(function(d,i) { return !data[i].disabled }));
12558
gEnter.select('.nv-distWrap').append('g')
12559
.attr('class', 'nv-distributionX');
12560
g.select('.nv-distributionX')
12561
.attr('transform', 'translate(0,' + y.range()[0] + ')')
12562
.datum(data.filter(function(d) { return !d.disabled }))
12568
.getData(scatter.y())
12570
.width(availableHeight)
12571
.color(data.map(function(d,i) {
12572
return d.color || color(d, i);
12573
}).filter(function(d,i) { return !data[i].disabled }));
12574
gEnter.select('.nv-distWrap').append('g')
12575
.attr('class', 'nv-distributionY');
12576
g.select('.nv-distributionY')
12577
.attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
12578
.datum(data.filter(function(d) { return !d.disabled }))
12582
//------------------------------------------------------------
12588
g.select('.nv-background')
12589
.attr('width', availableWidth)
12590
.attr('height', availableHeight)
12593
g.select('.nv-background').on('mousemove', updateFisheye);
12594
g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
12595
scatter.dispatch.on('elementClick.freezeFisheye', function() {
12596
pauseFisheye = !pauseFisheye;
12601
function updateFisheye() {
12602
if (pauseFisheye) {
12603
g.select('.nv-point-paths').style('pointer-events', 'all');
12607
g.select('.nv-point-paths').style('pointer-events', 'none' );
12609
var mouse = d3.mouse(this);
12610
x.distortion(fisheye).focus(mouse[0]);
12611
y.distortion(fisheye).focus(mouse[1]);
12613
g.select('.nv-scatterWrap')
12614
.datum(data.filter(function(d) { return !d.disabled }))
12618
g.select('.nv-x.nv-axis').call(xAxis);
12621
g.select('.nv-y.nv-axis').call(yAxis);
12623
g.select('.nv-distributionX')
12624
.datum(data.filter(function(d) { return !d.disabled }))
12626
g.select('.nv-distributionY')
12627
.datum(data.filter(function(d) { return !d.disabled }))
12633
//============================================================
12634
// Event Handling/Dispatching (in chart's scope)
12635
//------------------------------------------------------------
12637
controls.dispatch.on('legendClick', function(d,i) {
12638
d.disabled = !d.disabled;
12640
fisheye = d.disabled ? 0 : 2.5;
12641
g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12642
g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12645
x.distortion(fisheye).focus(0);
12646
y.distortion(fisheye).focus(0);
12648
g.select('.nv-scatterWrap').call(scatter);
12649
g.select('.nv-x.nv-axis').call(xAxis);
12650
g.select('.nv-y.nv-axis').call(yAxis);
12652
pauseFisheye = false;
12658
legend.dispatch.on('stateChange', function(newState) {
12660
dispatch.stateChange(state);
12665
scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12666
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12667
.attr('y1', e.pos[1] - availableHeight);
12668
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12669
.attr('x2', e.pos[0] + distX.size());
12671
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12672
dispatch.tooltipShow(e);
12675
dispatch.on('tooltipShow', function(e) {
12676
if (tooltips) showTooltip(e, that.parentNode);
12679
// Update chart from a state object passed to event handler
12680
dispatch.on('changeState', function(e) {
12682
if (typeof e.disabled !== 'undefined') {
12683
data.forEach(function(series,i) {
12684
series.disabled = e.disabled[i];
12687
state.disabled = e.disabled;
12693
//============================================================
12696
//store old scales for use in transitions on update
12707
//============================================================
12708
// Event Handling/Dispatching (out of chart's scope)
12709
//------------------------------------------------------------
12711
scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12712
dispatch.tooltipHide(e);
12714
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12716
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12717
.attr('x2', distY.size());
12719
dispatch.on('tooltipHide', function() {
12720
if (tooltips) nv.tooltip.cleanup();
12723
//============================================================
12726
//============================================================
12727
// Expose Public Variables
12728
//------------------------------------------------------------
12730
// expose chart's sub-components
12731
chart.dispatch = dispatch;
12732
chart.scatter = scatter;
12733
chart.legend = legend;
12734
chart.controls = controls;
12735
chart.xAxis = xAxis;
12736
chart.yAxis = yAxis;
12737
chart.distX = distX;
12738
chart.distY = distY;
12740
d3.rebind(chart, scatter, 'id', 'interactive', 'pointActive', 'x', 'y', 'shape', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'sizeRange', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'clipRadius', 'useVoronoi');
12742
chart.options = nv.utils.optionsFunc.bind(chart);
12744
chart.margin = function(_) {
12745
if (!arguments.length) return margin;
12746
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12747
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12748
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12749
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12753
chart.width = function(_) {
12754
if (!arguments.length) return width;
12759
chart.height = function(_) {
12760
if (!arguments.length) return height;
12765
chart.color = function(_) {
12766
if (!arguments.length) return color;
12767
color = nv.utils.getColor(_);
12768
legend.color(color);
12769
distX.color(color);
12770
distY.color(color);
12774
chart.showDistX = function(_) {
12775
if (!arguments.length) return showDistX;
12780
chart.showDistY = function(_) {
12781
if (!arguments.length) return showDistY;
12786
chart.showControls = function(_) {
12787
if (!arguments.length) return showControls;
12792
chart.showLegend = function(_) {
12793
if (!arguments.length) return showLegend;
12798
chart.showXAxis = function(_) {
12799
if (!arguments.length) return showXAxis;
12804
chart.showYAxis = function(_) {
12805
if (!arguments.length) return showYAxis;
12810
chart.rightAlignYAxis = function(_) {
12811
if(!arguments.length) return rightAlignYAxis;
12812
rightAlignYAxis = _;
12813
yAxis.orient( (_) ? 'right' : 'left');
12817
chart.fisheye = function(_) {
12818
if (!arguments.length) return fisheye;
12823
chart.tooltips = function(_) {
12824
if (!arguments.length) return tooltips;
12829
chart.tooltipContent = function(_) {
12830
if (!arguments.length) return tooltip;
12835
chart.tooltipXContent = function(_) {
12836
if (!arguments.length) return tooltipX;
12841
chart.tooltipYContent = function(_) {
12842
if (!arguments.length) return tooltipY;
12847
chart.state = function(_) {
12848
if (!arguments.length) return state;
12853
chart.defaultState = function(_) {
12854
if (!arguments.length) return defaultState;
12859
chart.noData = function(_) {
12860
if (!arguments.length) return noData;
12865
chart.transitionDuration = function(_) {
12866
if (!arguments.length) return transitionDuration;
12867
transitionDuration = _;
12871
//============================================================
12877
nv.models.sparkline = function() {
12879
//============================================================
12880
// Public Variables with Default Settings
12881
//------------------------------------------------------------
12883
var margin = {top: 2, right: 0, bottom: 2, left: 0}
12887
, x = d3.scale.linear()
12888
, y = d3.scale.linear()
12889
, getX = function(d) { return d.x }
12890
, getY = function(d) { return d.y }
12891
, color = nv.utils.getColor(['#000'])
12898
//============================================================
12901
function chart(selection) {
12902
selection.each(function(data) {
12903
var availableWidth = width - margin.left - margin.right,
12904
availableHeight = height - margin.top - margin.bottom,
12905
container = d3.select(this);
12908
//------------------------------------------------------------
12911
x .domain(xDomain || d3.extent(data, getX ))
12912
.range(xRange || [0, availableWidth]);
12914
y .domain(yDomain || d3.extent(data, getY ))
12915
.range(yRange || [availableHeight, 0]);
12917
//------------------------------------------------------------
12920
//------------------------------------------------------------
12921
// Setup containers and skeleton of chart
12923
var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
12924
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
12925
var gEnter = wrapEnter.append('g');
12926
var g = wrap.select('g');
12928
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
12930
//------------------------------------------------------------
12933
var paths = wrap.selectAll('path')
12934
.data(function(d) { return [d] });
12935
paths.enter().append('path');
12936
paths.exit().remove();
12938
.style('stroke', function(d,i) { return d.color || color(d, i) })
12939
.attr('d', d3.svg.line()
12940
.x(function(d,i) { return x(getX(d,i)) })
12941
.y(function(d,i) { return y(getY(d,i)) })
12945
// TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
12946
var points = wrap.selectAll('circle.nv-point')
12947
.data(function(data) {
12948
var yValues = data.map(function(d, i) { return getY(d,i); });
12949
function pointIndex(index) {
12951
var result = data[index];
12952
result.pointIndex = index;
12958
var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
12959
minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
12960
currentPoint = pointIndex(yValues.length - 1);
12961
return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
12963
points.enter().append('circle');
12964
points.exit().remove();
12966
.attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
12967
.attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
12969
.attr('class', function(d,i) {
12970
return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
12971
getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
12979
//============================================================
12980
// Expose Public Variables
12981
//------------------------------------------------------------
12982
chart.options = nv.utils.optionsFunc.bind(chart);
12984
chart.margin = function(_) {
12985
if (!arguments.length) return margin;
12986
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12987
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12988
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12989
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12993
chart.width = function(_) {
12994
if (!arguments.length) return width;
12999
chart.height = function(_) {
13000
if (!arguments.length) return height;
13005
chart.x = function(_) {
13006
if (!arguments.length) return getX;
13007
getX = d3.functor(_);
13011
chart.y = function(_) {
13012
if (!arguments.length) return getY;
13013
getY = d3.functor(_);
13017
chart.xScale = function(_) {
13018
if (!arguments.length) return x;
13023
chart.yScale = function(_) {
13024
if (!arguments.length) return y;
13029
chart.xDomain = function(_) {
13030
if (!arguments.length) return xDomain;
13035
chart.yDomain = function(_) {
13036
if (!arguments.length) return yDomain;
13041
chart.xRange = function(_) {
13042
if (!arguments.length) return xRange;
13047
chart.yRange = function(_) {
13048
if (!arguments.length) return yRange;
13053
chart.animate = function(_) {
13054
if (!arguments.length) return animate;
13059
chart.color = function(_) {
13060
if (!arguments.length) return color;
13061
color = nv.utils.getColor(_);
13065
//============================================================
13071
nv.models.sparklinePlus = function() {
13073
//============================================================
13074
// Public Variables with Default Settings
13075
//------------------------------------------------------------
13077
var sparkline = nv.models.sparkline();
13079
var margin = {top: 15, right: 100, bottom: 10, left: 50}
13086
, xTickFormat = d3.format(',r')
13087
, yTickFormat = d3.format(',.2f')
13089
, alignValue = true
13090
, rightAlignValue = false
13091
, noData = "No Data Available."
13094
//============================================================
13097
function chart(selection) {
13098
selection.each(function(data) {
13099
var container = d3.select(this);
13101
var availableWidth = (width || parseInt(container.style('width')) || 960)
13102
- margin.left - margin.right,
13103
availableHeight = (height || parseInt(container.style('height')) || 400)
13104
- margin.top - margin.bottom;
13108
chart.update = function() { chart(selection) };
13109
chart.container = this;
13112
//------------------------------------------------------------
13113
// Display No Data message if there's nothing to show.
13115
if (!data || !data.length) {
13116
var noDataText = container.selectAll('.nv-noData').data([noData]);
13118
noDataText.enter().append('text')
13119
.attr('class', 'nvd3 nv-noData')
13120
.attr('dy', '-.7em')
13121
.style('text-anchor', 'middle');
13124
.attr('x', margin.left + availableWidth / 2)
13125
.attr('y', margin.top + availableHeight / 2)
13126
.text(function(d) { return d });
13130
container.selectAll('.nv-noData').remove();
13133
var currentValue = sparkline.y()(data[data.length-1], data.length-1);
13135
//------------------------------------------------------------
13139
//------------------------------------------------------------
13142
x = sparkline.xScale();
13143
y = sparkline.yScale();
13145
//------------------------------------------------------------
13148
//------------------------------------------------------------
13149
// Setup containers and skeleton of chart
13151
var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
13152
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
13153
var gEnter = wrapEnter.append('g');
13154
var g = wrap.select('g');
13156
gEnter.append('g').attr('class', 'nv-sparklineWrap');
13157
gEnter.append('g').attr('class', 'nv-valueWrap');
13158
gEnter.append('g').attr('class', 'nv-hoverArea');
13160
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13162
//------------------------------------------------------------
13165
//------------------------------------------------------------
13166
// Main Chart Component(s)
13168
var sparklineWrap = g.select('.nv-sparklineWrap');
13171
.width(availableWidth)
13172
.height(availableHeight);
13177
//------------------------------------------------------------
13180
var valueWrap = g.select('.nv-valueWrap');
13182
var value = valueWrap.selectAll('.nv-currentValue')
13183
.data([currentValue]);
13185
value.enter().append('text').attr('class', 'nv-currentValue')
13186
.attr('dx', rightAlignValue ? -8 : 8)
13187
.attr('dy', '.9em')
13188
.style('text-anchor', rightAlignValue ? 'end' : 'start');
13191
.attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
13192
.attr('y', alignValue ? function(d) { return y(d) } : 0)
13193
.style('fill', sparkline.color()(data[data.length-1], data.length-1))
13194
.text(yTickFormat(currentValue));
13198
gEnter.select('.nv-hoverArea').append('rect')
13199
.on('mousemove', sparklineHover)
13200
.on('click', function() { paused = !paused })
13201
.on('mouseout', function() { index = []; updateValueLine(); });
13202
//.on('mouseout', function() { index = null; updateValueLine(); });
13204
g.select('.nv-hoverArea rect')
13205
.attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
13206
.attr('width', availableWidth + margin.left + margin.right)
13207
.attr('height', availableHeight + margin.top);
13211
function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
13212
if (paused) return;
13214
var hoverValue = g.selectAll('.nv-hoverValue').data(index)
13216
var hoverEnter = hoverValue.enter()
13217
.append('g').attr('class', 'nv-hoverValue')
13218
.style('stroke-opacity', 0)
13219
.style('fill-opacity', 0);
13222
.transition().duration(250)
13223
.style('stroke-opacity', 0)
13224
.style('fill-opacity', 0)
13228
.attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
13229
.transition().duration(250)
13230
.style('stroke-opacity', 1)
13231
.style('fill-opacity', 1);
13233
if (!index.length) return;
13235
hoverEnter.append('line')
13237
.attr('y1', -margin.top)
13239
.attr('y2', availableHeight);
13242
hoverEnter.append('text').attr('class', 'nv-xValue')
13244
.attr('y', -margin.top)
13245
.attr('text-anchor', 'end')
13246
.attr('dy', '.9em')
13249
g.select('.nv-hoverValue .nv-xValue')
13250
.text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
13252
hoverEnter.append('text').attr('class', 'nv-yValue')
13254
.attr('y', -margin.top)
13255
.attr('text-anchor', 'start')
13256
.attr('dy', '.9em')
13258
g.select('.nv-hoverValue .nv-yValue')
13259
.text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
13264
function sparklineHover() {
13265
if (paused) return;
13267
var pos = d3.mouse(this)[0] - margin.left;
13269
function getClosestIndex(data, x) {
13270
var distance = Math.abs(sparkline.x()(data[0], 0) - x);
13271
var closestIndex = 0;
13272
for (var i = 0; i < data.length; i++){
13273
if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
13274
distance = Math.abs(sparkline.x()(data[i], i) - x);
13278
return closestIndex;
13281
index = [getClosestIndex(data, Math.round(x.invert(pos)))];
13292
//============================================================
13293
// Expose Public Variables
13294
//------------------------------------------------------------
13296
// expose chart's sub-components
13297
chart.sparkline = sparkline;
13299
d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
13301
chart.options = nv.utils.optionsFunc.bind(chart);
13303
chart.margin = function(_) {
13304
if (!arguments.length) return margin;
13305
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13306
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13307
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13308
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13312
chart.width = function(_) {
13313
if (!arguments.length) return width;
13318
chart.height = function(_) {
13319
if (!arguments.length) return height;
13324
chart.xTickFormat = function(_) {
13325
if (!arguments.length) return xTickFormat;
13330
chart.yTickFormat = function(_) {
13331
if (!arguments.length) return yTickFormat;
13336
chart.showValue = function(_) {
13337
if (!arguments.length) return showValue;
13342
chart.alignValue = function(_) {
13343
if (!arguments.length) return alignValue;
13348
chart.rightAlignValue = function(_) {
13349
if (!arguments.length) return rightAlignValue;
13350
rightAlignValue = _;
13354
chart.noData = function(_) {
13355
if (!arguments.length) return noData;
13360
//============================================================
13366
nv.models.stackedArea = function() {
13368
//============================================================
13369
// Public Variables with Default Settings
13370
//------------------------------------------------------------
13372
var margin = {top: 0, right: 0, bottom: 0, left: 0}
13375
, color = nv.utils.defaultColor() // a function that computes the color
13376
, id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
13377
, getX = function(d) { return d.x } // accessor to get the x value from a data point
13378
, getY = function(d) { return d.y } // accessor to get the y value from a data point
13381
, order = 'default'
13382
, interpolate = 'linear' // controls the line interpolation
13383
, clipEdge = false // if true, masks lines within x and y scale
13384
, x //can be accessed via chart.xScale()
13385
, y //can be accessed via chart.yScale()
13386
, scatter = nv.models.scatter()
13387
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout')
13391
.size(2.2) // default size
13392
.sizeDomain([2.2,2.2]) // all the same size by default
13395
/************************************
13397
* 'wiggle' (stream)
13399
* 'expand' (normalize to 100%)
13400
* 'silhouette' (simple centered)
13403
* 'inside-out' (stream)
13404
* 'default' (input order)
13405
************************************/
13407
//============================================================
13410
function chart(selection) {
13411
selection.each(function(data) {
13412
var availableWidth = width - margin.left - margin.right,
13413
availableHeight = height - margin.top - margin.bottom,
13414
container = d3.select(this);
13416
//------------------------------------------------------------
13419
x = scatter.xScale();
13420
y = scatter.yScale();
13422
//------------------------------------------------------------
13424
var dataRaw = data;
13425
// Injecting point index into each point because d3.layout.stack().out does not give index
13426
data.forEach(function(aseries, i) {
13427
aseries.seriesIndex = i;
13428
aseries.values = aseries.values.map(function(d, j) {
13435
var dataFiltered = data.filter(function(series) {
13436
return !series.disabled;
13439
data = d3.layout.stack()
13442
.values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
13445
.out(function(d, y0, y) {
13446
var yHeight = (getY(d) === 0) ? 0 : y;
13455
//------------------------------------------------------------
13456
// Setup containers and skeleton of chart
13458
var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
13459
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
13460
var defsEnter = wrapEnter.append('defs');
13461
var gEnter = wrapEnter.append('g');
13462
var g = wrap.select('g');
13464
gEnter.append('g').attr('class', 'nv-areaWrap');
13465
gEnter.append('g').attr('class', 'nv-scatterWrap');
13467
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13469
//------------------------------------------------------------
13473
.width(availableWidth)
13474
.height(availableHeight)
13476
.y(function(d) { return d.display.y + d.display.y0 })
13478
.color(data.map(function(d,i) {
13479
return d.color || color(d, d.seriesIndex);
13483
var scatterWrap = g.select('.nv-scatterWrap')
13486
scatterWrap.call(scatter);
13488
defsEnter.append('clipPath')
13489
.attr('id', 'nv-edge-clip-' + id)
13492
wrap.select('#nv-edge-clip-' + id + ' rect')
13493
.attr('width', availableWidth)
13494
.attr('height', availableHeight);
13496
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
13498
var area = d3.svg.area()
13499
.x(function(d,i) { return x(getX(d,i)) })
13501
return y(d.display.y0)
13504
return y(d.display.y + d.display.y0)
13506
.interpolate(interpolate);
13508
var zeroArea = d3.svg.area()
13509
.x(function(d,i) { return x(getX(d,i)) })
13510
.y0(function(d) { return y(d.display.y0) })
13511
.y1(function(d) { return y(d.display.y0) });
13514
var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
13515
.data(function(d) { return d });
13517
path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
13518
.attr('d', function(d,i){
13519
return zeroArea(d.values, d.seriesIndex);
13521
.on('mouseover', function(d,i) {
13522
d3.select(this).classed('hover', true);
13523
dispatch.areaMouseover({
13526
pos: [d3.event.pageX, d3.event.pageY],
13527
seriesIndex: d.seriesIndex
13530
.on('mouseout', function(d,i) {
13531
d3.select(this).classed('hover', false);
13532
dispatch.areaMouseout({
13535
pos: [d3.event.pageX, d3.event.pageY],
13536
seriesIndex: d.seriesIndex
13539
.on('click', function(d,i) {
13540
d3.select(this).classed('hover', false);
13541
dispatch.areaClick({
13544
pos: [d3.event.pageX, d3.event.pageY],
13545
seriesIndex: d.seriesIndex
13549
path.exit().remove();
13552
.style('fill', function(d,i){
13553
return d.color || color(d, d.seriesIndex)
13555
.style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
13557
.attr('d', function(d,i) {
13558
return area(d.values,i)
13563
//============================================================
13564
// Event Handling/Dispatching (in chart's scope)
13565
//------------------------------------------------------------
13567
scatter.dispatch.on('elementMouseover.area', function(e) {
13568
g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
13570
scatter.dispatch.on('elementMouseout.area', function(e) {
13571
g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
13574
//============================================================
13575
//Special offset functions
13576
chart.d3_stackedOffset_stackPercent = function(stackData) {
13577
var n = stackData.length, //How many series
13578
m = stackData[0].length, //how many points per series
13585
for (j = 0; j < m; ++j) { //Looping through all points
13586
for (i = 0, o = 0; i < dataRaw.length; i++) //looping through series'
13587
o += getY(dataRaw[i].values[j]) //total value of all points at a certian point in time.
13589
if (o) for (i = 0; i < n; i++)
13590
stackData[i][j][1] /= o;
13592
for (i = 0; i < n; i++)
13593
stackData[i][j][1] = k;
13595
for (j = 0; j < m; ++j) y0[j] = 0;
13606
//============================================================
13607
// Event Handling/Dispatching (out of chart's scope)
13608
//------------------------------------------------------------
13610
scatter.dispatch.on('elementClick.area', function(e) {
13611
dispatch.areaClick(e);
13613
scatter.dispatch.on('elementMouseover.tooltip', function(e) {
13614
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13615
dispatch.tooltipShow(e);
13617
scatter.dispatch.on('elementMouseout.tooltip', function(e) {
13618
dispatch.tooltipHide(e);
13621
//============================================================
13623
//============================================================
13624
// Global getters and setters
13625
//------------------------------------------------------------
13627
chart.dispatch = dispatch;
13628
chart.scatter = scatter;
13630
d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
13631
'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights');
13633
chart.options = nv.utils.optionsFunc.bind(chart);
13635
chart.x = function(_) {
13636
if (!arguments.length) return getX;
13637
getX = d3.functor(_);
13641
chart.y = function(_) {
13642
if (!arguments.length) return getY;
13643
getY = d3.functor(_);
13647
chart.margin = function(_) {
13648
if (!arguments.length) return margin;
13649
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13650
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13651
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13652
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13656
chart.width = function(_) {
13657
if (!arguments.length) return width;
13662
chart.height = function(_) {
13663
if (!arguments.length) return height;
13668
chart.clipEdge = function(_) {
13669
if (!arguments.length) return clipEdge;
13674
chart.color = function(_) {
13675
if (!arguments.length) return color;
13676
color = nv.utils.getColor(_);
13680
chart.offset = function(_) {
13681
if (!arguments.length) return offset;
13686
chart.order = function(_) {
13687
if (!arguments.length) return order;
13692
//shortcut for offset + order
13693
chart.style = function(_) {
13694
if (!arguments.length) return style;
13699
chart.offset('zero');
13700
chart.order('default');
13703
chart.offset('wiggle');
13704
chart.order('inside-out');
13706
case 'stream-center':
13707
chart.offset('silhouette');
13708
chart.order('inside-out');
13711
chart.offset('expand');
13712
chart.order('default');
13714
case 'stack_percent':
13715
chart.offset(chart.d3_stackedOffset_stackPercent);
13716
chart.order('default');
13723
chart.interpolate = function(_) {
13724
if (!arguments.length) return interpolate;
13728
//============================================================
13734
nv.models.stackedAreaChart = function() {
13736
//============================================================
13737
// Public Variables with Default Settings
13738
//------------------------------------------------------------
13740
var stacked = nv.models.stackedArea()
13741
, xAxis = nv.models.axis()
13742
, yAxis = nv.models.axis()
13743
, legend = nv.models.legend()
13744
, controls = nv.models.legend()
13745
, interactiveLayer = nv.interactiveGuideline()
13748
var margin = {top: 30, right: 25, bottom: 50, left: 60}
13751
, color = nv.utils.defaultColor() // a function that takes in d, i and returns color
13752
, showControls = true
13753
, showLegend = true
13756
, rightAlignYAxis = false
13757
, useInteractiveGuideline = false
13759
, tooltip = function(key, x, y, e, graph) {
13760
return '<h3>' + key + '</h3>' +
13761
'<p>' + y + ' on ' + x + '</p>'
13763
, x //can be accessed via chart.xScale()
13764
, y //can be accessed via chart.yScale()
13765
, yAxisTickFormat = d3.format(',.2f')
13766
, state = { style: stacked.style() }
13767
, defaultState = null
13768
, noData = 'No Data Available.'
13769
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
13770
, controlWidth = 250
13771
, cData = ['Stacked','Stream','Expanded']
13772
, controlLabels = {}
13773
, transitionDuration = 250
13781
.orient((rightAlignYAxis) ? 'right' : 'left')
13784
controls.updateState(false);
13785
//============================================================
13788
//============================================================
13789
// Private Variables
13790
//------------------------------------------------------------
13792
var showTooltip = function(e, offsetElement) {
13793
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
13794
top = e.pos[1] + ( offsetElement.offsetTop || 0),
13795
x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
13796
y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
13797
content = tooltip(e.series.key, x, y, e, chart);
13799
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
13802
//============================================================
13805
function chart(selection) {
13806
selection.each(function(data) {
13807
var container = d3.select(this),
13810
var availableWidth = (width || parseInt(container.style('width')) || 960)
13811
- margin.left - margin.right,
13812
availableHeight = (height || parseInt(container.style('height')) || 400)
13813
- margin.top - margin.bottom;
13815
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
13816
chart.container = this;
13818
//set state.disabled
13819
state.disabled = data.map(function(d) { return !!d.disabled });
13821
if (!defaultState) {
13824
for (key in state) {
13825
if (state[key] instanceof Array)
13826
defaultState[key] = state[key].slice(0);
13828
defaultState[key] = state[key];
13832
//------------------------------------------------------------
13833
// Display No Data message if there's nothing to show.
13835
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
13836
var noDataText = container.selectAll('.nv-noData').data([noData]);
13838
noDataText.enter().append('text')
13839
.attr('class', 'nvd3 nv-noData')
13840
.attr('dy', '-.7em')
13841
.style('text-anchor', 'middle');
13844
.attr('x', margin.left + availableWidth / 2)
13845
.attr('y', margin.top + availableHeight / 2)
13846
.text(function(d) { return d });
13850
container.selectAll('.nv-noData').remove();
13853
//------------------------------------------------------------
13856
//------------------------------------------------------------
13859
x = stacked.xScale();
13860
y = stacked.yScale();
13862
//------------------------------------------------------------
13865
//------------------------------------------------------------
13866
// Setup containers and skeleton of chart
13868
var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
13869
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
13870
var g = wrap.select('g');
13872
gEnter.append("rect").style("opacity",0);
13873
gEnter.append('g').attr('class', 'nv-x nv-axis');
13874
gEnter.append('g').attr('class', 'nv-y nv-axis');
13875
gEnter.append('g').attr('class', 'nv-stackedWrap');
13876
gEnter.append('g').attr('class', 'nv-legendWrap');
13877
gEnter.append('g').attr('class', 'nv-controlsWrap');
13878
gEnter.append('g').attr('class', 'nv-interactive');
13880
g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
13881
//------------------------------------------------------------
13885
var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
13887
.width(legendWidth);
13889
g.select('.nv-legendWrap')
13893
if ( margin.top != legend.height()) {
13894
margin.top = legend.height();
13895
availableHeight = (height || parseInt(container.style('height')) || 400)
13896
- margin.top - margin.bottom;
13899
g.select('.nv-legendWrap')
13900
.attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
13903
//------------------------------------------------------------
13906
//------------------------------------------------------------
13909
if (showControls) {
13910
var controlsData = [
13912
key: controlLabels.stacked || 'Stacked',
13913
metaKey: 'Stacked',
13914
disabled: stacked.style() != 'stack',
13918
key: controlLabels.stream || 'Stream',
13920
disabled: stacked.style() != 'stream',
13924
key: controlLabels.expanded || 'Expanded',
13925
metaKey: 'Expanded',
13926
disabled: stacked.style() != 'expand',
13930
key: controlLabels.stack_percent || 'Stack %',
13931
metaKey: 'Stack_Percent',
13932
disabled: stacked.style() != 'stack_percent',
13933
style: 'stack_percent'
13937
controlWidth = (cData.length/3) * 260;
13939
controlsData = controlsData.filter(function(d) {
13940
return cData.indexOf(d.metaKey) !== -1;
13944
.width( controlWidth )
13945
.color(['#444', '#444', '#444']);
13947
g.select('.nv-controlsWrap')
13948
.datum(controlsData)
13952
if ( margin.top != Math.max(controls.height(), legend.height()) ) {
13953
margin.top = Math.max(controls.height(), legend.height());
13954
availableHeight = (height || parseInt(container.style('height')) || 400)
13955
- margin.top - margin.bottom;
13959
g.select('.nv-controlsWrap')
13960
.attr('transform', 'translate(0,' + (-margin.top) +')');
13963
//------------------------------------------------------------
13966
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13968
if (rightAlignYAxis) {
13969
g.select(".nv-y.nv-axis")
13970
.attr("transform", "translate(" + availableWidth + ",0)");
13973
//------------------------------------------------------------
13974
// Main Chart Component(s)
13976
//------------------------------------------------------------
13977
//Set up interactive layer
13978
if (useInteractiveGuideline) {
13980
.width(availableWidth)
13981
.height(availableHeight)
13982
.margin({left: margin.left, top: margin.top})
13983
.svgContainer(container)
13985
wrap.select(".nv-interactive").call(interactiveLayer);
13989
.width(availableWidth)
13990
.height(availableHeight)
13992
var stackedWrap = g.select('.nv-stackedWrap')
13995
stackedWrap.transition().call(stacked);
13997
//------------------------------------------------------------
14000
//------------------------------------------------------------
14006
.ticks( availableWidth / 100 )
14007
.tickSize( -availableHeight, 0);
14009
g.select('.nv-x.nv-axis')
14010
.attr('transform', 'translate(0,' + availableHeight + ')');
14012
g.select('.nv-x.nv-axis')
14013
.transition().duration(0)
14020
.ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
14021
.tickSize(-availableWidth, 0)
14022
.setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
14023
? d3.format('%') : yAxisTickFormat);
14025
g.select('.nv-y.nv-axis')
14026
.transition().duration(0)
14030
//------------------------------------------------------------
14033
//============================================================
14034
// Event Handling/Dispatching (in chart's scope)
14035
//------------------------------------------------------------
14037
stacked.dispatch.on('areaClick.toggle', function(e) {
14038
if (data.filter(function(d) { return !d.disabled }).length === 1)
14039
data.forEach(function(d) {
14040
d.disabled = false;
14043
data.forEach(function(d,i) {
14044
d.disabled = (i != e.seriesIndex);
14047
state.disabled = data.map(function(d) { return !!d.disabled });
14048
dispatch.stateChange(state);
14053
legend.dispatch.on('stateChange', function(newState) {
14054
state.disabled = newState.disabled;
14055
dispatch.stateChange(state);
14059
controls.dispatch.on('legendClick', function(d,i) {
14060
if (!d.disabled) return;
14062
controlsData = controlsData.map(function(s) {
14066
d.disabled = false;
14068
stacked.style(d.style);
14071
state.style = stacked.style();
14072
dispatch.stateChange(state);
14078
interactiveLayer.dispatch.on('elementMousemove', function(e) {
14079
stacked.clearHighlights();
14080
var singlePoint, pointIndex, pointXLocation, allData = [];
14082
.filter(function(series, i) {
14083
series.seriesIndex = i;
14084
return !series.disabled;
14086
.forEach(function(series,i) {
14087
pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
14088
stacked.highlightPoint(i, pointIndex, true);
14089
var point = series.values[pointIndex];
14090
if (typeof point === 'undefined') return;
14091
if (typeof singlePoint === 'undefined') singlePoint = point;
14092
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
14094
//If we are in 'expand' mode, use the stacked percent value instead of raw value.
14095
var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
14098
value: tooltipValue,
14099
color: color(series,series.seriesIndex),
14100
stackedValue: point.display
14106
//Highlight the tooltip entry based on which stack the mouse is closest to.
14107
if (allData.length > 2) {
14108
var yValue = chart.yScale().invert(e.mouseY);
14109
var yDistMax = Infinity, indexToHighlight = null;
14110
allData.forEach(function(series,i) {
14112
//To handle situation where the stacked area chart is negative, we need to use absolute values
14113
//when checking if the mouse Y value is within the stack area.
14114
yValue = Math.abs(yValue);
14115
var stackedY0 = Math.abs(series.stackedValue.y0);
14116
var stackedY = Math.abs(series.stackedValue.y);
14117
if ( yValue >= stackedY0 && yValue <= (stackedY + stackedY0))
14119
indexToHighlight = i;
14123
if (indexToHighlight != null)
14124
allData[indexToHighlight].highlight = true;
14127
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
14129
//If we are in 'expand' mode, force the format to be a percentage.
14130
var valueFormatter = (stacked.style() == 'expand') ?
14131
function(d,i) {return d3.format(".1%")(d);} :
14132
function(d,i) {return yAxis.tickFormat()(d); };
14133
interactiveLayer.tooltip
14134
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
14135
.chartContainer(that.parentNode)
14137
.valueFormatter(valueFormatter)
14145
interactiveLayer.renderGuideLine(pointXLocation);
14149
interactiveLayer.dispatch.on("elementMouseout",function(e) {
14150
dispatch.tooltipHide();
14151
stacked.clearHighlights();
14155
dispatch.on('tooltipShow', function(e) {
14156
if (tooltips) showTooltip(e, that.parentNode);
14159
// Update chart from a state object passed to event handler
14160
dispatch.on('changeState', function(e) {
14162
if (typeof e.disabled !== 'undefined' && data.length === e.disabled.length) {
14163
data.forEach(function(series,i) {
14164
series.disabled = e.disabled[i];
14167
state.disabled = e.disabled;
14170
if (typeof e.style !== 'undefined') {
14171
stacked.style(e.style);
14184
//============================================================
14185
// Event Handling/Dispatching (out of chart's scope)
14186
//------------------------------------------------------------
14188
stacked.dispatch.on('tooltipShow', function(e) {
14189
//disable tooltips when value ~= 0
14190
//// TODO: consider removing points from voronoi that have 0 value instead of this hack
14192
if (!Math.round(stacked.y()(e.point) * 100)) { // 100 will not be good for very small numbers... will have to think about making this valu dynamic, based on data range
14193
setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
14198
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
14199
dispatch.tooltipShow(e);
14202
stacked.dispatch.on('tooltipHide', function(e) {
14203
dispatch.tooltipHide(e);
14206
dispatch.on('tooltipHide', function() {
14207
if (tooltips) nv.tooltip.cleanup();
14210
//============================================================
14213
//============================================================
14214
// Expose Public Variables
14215
//------------------------------------------------------------
14217
// expose chart's sub-components
14218
chart.dispatch = dispatch;
14219
chart.stacked = stacked;
14220
chart.legend = legend;
14221
chart.controls = controls;
14222
chart.xAxis = xAxis;
14223
chart.yAxis = yAxis;
14224
chart.interactiveLayer = interactiveLayer;
14226
d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
14228
chart.options = nv.utils.optionsFunc.bind(chart);
14230
chart.margin = function(_) {
14231
if (!arguments.length) return margin;
14232
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
14233
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
14234
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14235
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
14239
chart.width = function(_) {
14240
if (!arguments.length) return width;
14245
chart.height = function(_) {
14246
if (!arguments.length) return height;
14251
chart.color = function(_) {
14252
if (!arguments.length) return color;
14253
color = nv.utils.getColor(_);
14254
legend.color(color);
14255
stacked.color(color);
14259
chart.showControls = function(_) {
14260
if (!arguments.length) return showControls;
14265
chart.showLegend = function(_) {
14266
if (!arguments.length) return showLegend;
14271
chart.showXAxis = function(_) {
14272
if (!arguments.length) return showXAxis;
14277
chart.showYAxis = function(_) {
14278
if (!arguments.length) return showYAxis;
14283
chart.rightAlignYAxis = function(_) {
14284
if(!arguments.length) return rightAlignYAxis;
14285
rightAlignYAxis = _;
14286
yAxis.orient( (_) ? 'right' : 'left');
14290
chart.useInteractiveGuideline = function(_) {
14291
if(!arguments.length) return useInteractiveGuideline;
14292
useInteractiveGuideline = _;
14294
chart.interactive(false);
14295
chart.useVoronoi(false);
14300
chart.tooltip = function(_) {
14301
if (!arguments.length) return tooltip;
14306
chart.tooltips = function(_) {
14307
if (!arguments.length) return tooltips;
14312
chart.tooltipContent = function(_) {
14313
if (!arguments.length) return tooltip;
14318
chart.state = function(_) {
14319
if (!arguments.length) return state;
14324
chart.defaultState = function(_) {
14325
if (!arguments.length) return defaultState;
14330
chart.noData = function(_) {
14331
if (!arguments.length) return noData;
14336
chart.transitionDuration = function(_) {
14337
if (!arguments.length) return transitionDuration;
14338
transitionDuration = _;
14342
chart.controlsData = function(_) {
14343
if (!arguments.length) return cData;
14348
chart.controlLabels = function(_) {
14349
if (!arguments.length) return controlLabels;
14350
if (typeof _ !== 'object') return controlLabels;
14355
yAxis.setTickFormat = yAxis.tickFormat;
14357
yAxis.tickFormat = function(_) {
14358
if (!arguments.length) return yAxisTickFormat;
14359
yAxisTickFormat = _;
14364
//============================================================