Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
braverock
GitHub Repository: braverock/portfolioanalytics
Path: blob/master/sandbox/RFinance2014/libraries/widgets/nvd3/js/nv.d3.js
1433 views
1
(function(){
2
3
var nv = window.nv || {};
4
5
6
nv.version = '1.1.10b';
7
nv.dev = true //set false when in production
8
9
window.nv = nv;
10
11
nv.tooltip = {}; // For the tooltip system
12
nv.utils = {}; // Utility subsystem
13
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
17
18
nv.dispatch = d3.dispatch('render_start', 'render_end');
19
20
// *************************************************************************
21
// Development render timers - disabled if dev = false
22
23
if (nv.dev) {
24
nv.dispatch.on('render_start', function(e) {
25
nv.logs.startTime = +new Date();
26
});
27
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
32
});
33
}
34
35
// ********************************************
36
// Public Core NV functions
37
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.
42
nv.log = function() {
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);
48
}
49
return arguments[arguments.length - 1];
50
};
51
52
53
nv.render = function render(step) {
54
step = step || 1; // number of graphs to generate in each timeout loop
55
56
nv.render.active = true;
57
nv.dispatch.render_start();
58
59
setTimeout(function() {
60
var chart, graph;
61
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);
66
}
67
68
nv.render.queue.splice(0, i);
69
70
if (nv.render.queue.length) setTimeout(arguments.callee, 0);
71
else { nv.render.active = false; nv.dispatch.render_end(); }
72
}, 0);
73
};
74
75
nv.render.active = false;
76
nv.render.queue = [];
77
78
nv.addGraph = function(obj) {
79
if (typeof arguments[0] === typeof(Function))
80
obj = {generate: arguments[0], callback: arguments[1]};
81
82
nv.render.queue.push(obj);
83
84
if (!nv.render.active) nv.render();
85
};
86
87
nv.identity = function(d) { return d; };
88
89
nv.strip = function(s) { return s.replace(/(\s|&)/g,''); };
90
91
function daysInMonth(month,year) {
92
return (new Date(year, month+1, 0)).getDate();
93
}
94
95
function d3_time_range(floor, step, number) {
96
return function(t0, t1, dt) {
97
var time = floor(t0), times = [];
98
if (time < t0) step(time);
99
if (dt > 1) {
100
while (time < t1) {
101
var date = new Date(+time);
102
if ((number(date) % dt === 0)) times.push(date);
103
step(time);
104
}
105
} else {
106
while (time < t1) { times.push(new Date(+time)); step(time); }
107
}
108
return times;
109
};
110
}
111
112
d3.time.monthEnd = function(date) {
113
return new Date(date.getFullYear(), date.getMonth(), 0);
114
};
115
116
d3.time.monthEnds = d3_time_range(d3.time.monthEnd, function(date) {
117
date.setUTCDate(date.getUTCDate() + 1);
118
date.setDate(daysInMonth(date.getMonth() + 1, date.getFullYear()));
119
}, function(date) {
120
return date.getMonth();
121
}
122
);
123
124
/* Utility class to handle creation of an interactive layer.
125
This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
126
containing the X-coordinate. It can also render a vertical line where the mouse is located.
127
128
dispatch.elementMousemove is the important event to latch onto. It is fired whenever the mouse moves over
129
the rectangle. The dispatch is given one object which contains the mouseX/Y location.
130
It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
131
*/
132
nv.interactiveGuideline = function() {
133
"use strict";
134
var tooltip = nv.models.tooltip();
135
//Public settings
136
var width = null
137
, height = null
138
//Please pass in the bounding chart's top and left margins
139
//This is important for calculating the correct mouseX/Y positions.
140
, margin = {left: 0, top: 0}
141
, xScale = d3.scale.linear()
142
, yScale = d3.scale.linear()
143
, dispatch = d3.dispatch('elementMousemove', 'elementMouseout')
144
, showGuideLine = true
145
, svgContainer = null
146
//Must pass in the bounding chart's <svg> container.
147
//The mousemove event is attached to this container.
148
;
149
150
//Private variables
151
var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1 //Check user-agent for Microsoft Internet Explorer.
152
;
153
154
155
function layer(selection) {
156
selection.each(function(data) {
157
var container = d3.select(this);
158
159
var availableWidth = (width || 960), availableHeight = (height || 400);
160
161
var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]);
162
var wrapEnter = wrap.enter()
163
.append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
164
165
166
wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
167
168
if (!svgContainer) {
169
return;
170
}
171
172
function mouseHandler() {
173
var d3mouse = d3.mouse(this);
174
var mouseX = d3mouse[0];
175
var mouseY = d3mouse[1];
176
var subtractMargin = true;
177
var mouseOutAnyReason = false;
178
if (isMSIE) {
179
/*
180
D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
181
d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
182
over a rect in IE 10.
183
However, d3.event.offsetX/Y also returns the mouse coordinates
184
relative to the triggering <rect>. So we use offsetX/Y on IE.
185
*/
186
mouseX = d3.event.offsetX;
187
mouseY = d3.event.offsetY;
188
189
/*
190
On IE, if you attach a mouse event listener to the <svg> container,
191
it will actually trigger it for all the child elements (like <path>, <circle>, etc).
192
When this happens on IE, the offsetX/Y is set to where ever the child element
193
is located.
194
As a result, we do NOT need to subtract margins to figure out the mouse X/Y
195
position under this scenario. Removing the line below *will* cause
196
the interactive layer to not work right on IE.
197
*/
198
if(d3.event.target.tagName !== "svg")
199
subtractMargin = false;
200
201
if (d3.event.target.className.baseVal.match("nv-legend"))
202
mouseOutAnyReason = true;
203
204
}
205
206
if(subtractMargin) {
207
mouseX -= margin.left;
208
mouseY -= margin.top;
209
}
210
211
/* If mouseX/Y is outside of the chart's bounds,
212
trigger a mouseOut event.
213
*/
214
if (mouseX < 0 || mouseY < 0
215
|| mouseX > availableWidth || mouseY > availableHeight
216
|| (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
217
|| mouseOutAnyReason
218
)
219
{
220
if (isMSIE) {
221
if (d3.event.relatedTarget
222
&& d3.event.relatedTarget.ownerSVGElement === undefined
223
&& d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
224
return;
225
}
226
}
227
dispatch.elementMouseout({
228
mouseX: mouseX,
229
mouseY: mouseY
230
});
231
layer.renderGuideLine(null); //hide the guideline
232
return;
233
}
234
235
var pointXValue = xScale.invert(mouseX);
236
dispatch.elementMousemove({
237
mouseX: mouseX,
238
mouseY: mouseY,
239
pointXValue: pointXValue
240
});
241
}
242
243
svgContainer
244
.on("mousemove",mouseHandler, true)
245
.on("mouseout",mouseHandler,true)
246
;
247
248
//Draws a vertical guideline at the given X postion.
249
layer.renderGuideLine = function(x) {
250
if (!showGuideLine) return;
251
var line = wrap.select(".nv-interactiveGuideLine")
252
.selectAll("line")
253
.data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
254
255
line.enter()
256
.append("line")
257
.attr("class", "nv-guideline")
258
.attr("x1", function(d) { return d;})
259
.attr("x2", function(d) { return d;})
260
.attr("y1", availableHeight)
261
.attr("y2",0)
262
;
263
line.exit().remove();
264
265
}
266
});
267
}
268
269
layer.dispatch = dispatch;
270
layer.tooltip = tooltip;
271
272
layer.margin = function(_) {
273
if (!arguments.length) return margin;
274
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
275
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
276
return layer;
277
};
278
279
layer.width = function(_) {
280
if (!arguments.length) return width;
281
width = _;
282
return layer;
283
};
284
285
layer.height = function(_) {
286
if (!arguments.length) return height;
287
height = _;
288
return layer;
289
};
290
291
layer.xScale = function(_) {
292
if (!arguments.length) return xScale;
293
xScale = _;
294
return layer;
295
};
296
297
layer.showGuideLine = function(_) {
298
if (!arguments.length) return showGuideLine;
299
showGuideLine = _;
300
return layer;
301
};
302
303
layer.svgContainer = function(_) {
304
if (!arguments.length) return svgContainer;
305
svgContainer = _;
306
return layer;
307
};
308
309
310
return layer;
311
};
312
313
/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
314
This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
315
316
For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
317
Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10. But interactiveBisect will return 5
318
because 28 is closer to 30 than 10.
319
320
Unit tests can be found in: interactiveBisectTest.html
321
322
Has the following known issues:
323
* Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
324
* Won't work if there are duplicate x coordinate values.
325
*/
326
nv.interactiveBisect = function (values, searchVal, xAccessor) {
327
"use strict";
328
if (! values instanceof Array) return null;
329
if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;}
330
331
var bisect = d3.bisector(xAccessor).left;
332
var index = d3.max([0, bisect(values,searchVal) - 1]);
333
var currentValue = xAccessor(values[index], index);
334
if (typeof currentValue === 'undefined') currentValue = index;
335
336
if (currentValue === searchVal) return index; //found exact match
337
338
var nextIndex = d3.min([index+1, values.length - 1]);
339
var nextValue = xAccessor(values[nextIndex], nextIndex);
340
if (typeof nextValue === 'undefined') nextValue = nextIndex;
341
342
if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal))
343
return index;
344
else
345
return nextIndex
346
};/* Tooltip rendering model for nvd3 charts.
347
window.nv.models.tooltip is the updated,new way to render tooltips.
348
349
window.nv.tooltip.show is the old tooltip code.
350
window.nv.tooltip.* also has various helper methods.
351
*/
352
(function() {
353
"use strict";
354
window.nv.tooltip = {};
355
356
/* Model which can be instantiated to handle tooltip rendering.
357
Example usage:
358
var tip = nv.models.tooltip().gravity('w').distance(23)
359
.data(myDataObject);
360
361
tip(); //just invoke the returned function to render tooltip.
362
*/
363
window.nv.models.tooltip = function() {
364
var content = null //HTML contents of the tooltip. If null, the content is generated via the data variable.
365
, data = null /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
366
Format of data:
367
{
368
key: "Date",
369
value: "August 2009",
370
series: [
371
{
372
key: "Series 1",
373
value: "Value 1",
374
color: "#000"
375
},
376
{
377
key: "Series 2",
378
value: "Value 2",
379
color: "#00f"
380
}
381
]
382
383
}
384
385
*/
386
, gravity = 'w' //Can be 'n','s','e','w'. Determines how tooltip is positioned.
387
, distance = 50 //Distance to offset tooltip from the mouse location.
388
, snapDistance = 25 //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
389
, fixedTop = null //If not null, this fixes the top position of the tooltip.
390
, classes = null //Attaches additional CSS classes to the tooltip DIV that is created.
391
, chartContainer = null //Parent DIV, of the SVG Container that holds the chart.
392
, position = {left: null, top: null} //Relative position of the tooltip inside chartContainer.
393
, enabled = true //True -> tooltips are rendered. False -> don't render tooltips.
394
//Generates a unique id when you create a new tooltip() object
395
, id = "nvtooltip-" + Math.floor(Math.random() * 100000)
396
;
397
398
//CSS class to specify whether element should not have mouse events.
399
var nvPointerEventsClass = "nv-pointer-events-none";
400
401
//Format function for the tooltip values column
402
var valueFormatter = function(d,i) {
403
return d;
404
};
405
406
//Format function for the tooltip header value.
407
var headerFormatter = function(d) {
408
return d;
409
};
410
411
//By default, the tooltip model renders a beautiful table inside a DIV.
412
//You can override this function if a custom tooltip is desired.
413
var contentGenerator = function(d) {
414
if (content != null) return content;
415
416
if (d == null) return '';
417
418
var html = "<table><thead><tr><td colspan='3'><strong class='x-value'>" + headerFormatter(d.value) + "</strong></td></tr></thead><tbody>";
419
if (d.series instanceof Array) {
420
d.series.forEach(function(item, i) {
421
html += "<tr>";
422
html += "<td class='legend-color-guide'><div style='background-color: " + item.color + ";'></div></td>";
423
html += "<td class='key'>" + item.key + ":</td>";
424
html += "<td class='value'>" + valueFormatter(item.value,i) + "</td></tr>";
425
});
426
}
427
html += "</tbody></table>";
428
return html;
429
};
430
431
var dataSeriesExists = function(d) {
432
if (d && d.series && d.series.length > 0) return true;
433
434
return false;
435
};
436
437
//In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
438
function convertViewBoxRatio() {
439
if (chartContainer) {
440
var svg = d3.select(chartContainer);
441
if (svg.node().tagName !== "svg") {
442
svg = svg.select("svg");
443
}
444
var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
445
if (viewBox) {
446
viewBox = viewBox.split(' ');
447
var ratio = parseInt(svg.style('width')) / viewBox[2];
448
449
position.left = position.left * ratio;
450
position.top = position.top * ratio;
451
}
452
}
453
}
454
455
//Creates new tooltip container, or uses existing one on DOM.
456
function getTooltipContainer(newContent) {
457
var body;
458
if (chartContainer)
459
body = d3.select(chartContainer);
460
else
461
body = d3.select("body");
462
463
var container = body.select(".nvtooltip");
464
if (container.node() === null) {
465
//Create new tooltip div if it doesn't exist on DOM.
466
container = body.append("div")
467
.attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
468
.attr("id",id)
469
;
470
}
471
472
473
container.node().innerHTML = newContent;
474
container.style("top",0).style("left",0).style("opacity",0);
475
container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
476
container.classed(nvPointerEventsClass,true);
477
return container.node();
478
}
479
480
481
482
//Draw the tooltip onto the DOM.
483
function nvtooltip() {
484
if (!enabled) return;
485
if (!dataSeriesExists(data)) return;
486
487
convertViewBoxRatio();
488
489
var left = position.left;
490
var top = (fixedTop != null) ? fixedTop : position.top;
491
var container = getTooltipContainer(contentGenerator(data));
492
493
if (chartContainer) {
494
var svgComp = chartContainer.getElementsByTagName("svg")[0];
495
var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
496
var svgOffset = {left:0,top:0};
497
if (svgComp) {
498
var svgBound = svgComp.getBoundingClientRect();
499
var chartBound = chartContainer.getBoundingClientRect();
500
svgOffset.top = Math.abs(svgBound.top - chartBound.top);
501
svgOffset.left = Math.abs(svgBound.left - chartBound.left);
502
}
503
//If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
504
//You need to also add any offset between the <svg> element and its containing <div>
505
//Finally, add any offset of the containing <div> on the whole page.
506
left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
507
top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
508
}
509
510
if (snapDistance && snapDistance > 0) {
511
top = Math.floor(top/snapDistance) * snapDistance;
512
}
513
514
nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
515
return nvtooltip;
516
};
517
518
nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
519
520
nvtooltip.content = function(_) {
521
if (!arguments.length) return content;
522
content = _;
523
return nvtooltip;
524
};
525
526
nvtooltip.contentGenerator = function(_) {
527
if (!arguments.length) return contentGenerator;
528
if (typeof _ === 'function') {
529
contentGenerator = _;
530
}
531
return nvtooltip;
532
};
533
534
nvtooltip.data = function(_) {
535
if (!arguments.length) return data;
536
data = _;
537
return nvtooltip;
538
};
539
540
nvtooltip.gravity = function(_) {
541
if (!arguments.length) return gravity;
542
gravity = _;
543
return nvtooltip;
544
};
545
546
nvtooltip.distance = function(_) {
547
if (!arguments.length) return distance;
548
distance = _;
549
return nvtooltip;
550
};
551
552
nvtooltip.snapDistance = function(_) {
553
if (!arguments.length) return snapDistance;
554
snapDistance = _;
555
return nvtooltip;
556
};
557
558
nvtooltip.classes = function(_) {
559
if (!arguments.length) return classes;
560
classes = _;
561
return nvtooltip;
562
};
563
564
nvtooltip.chartContainer = function(_) {
565
if (!arguments.length) return chartContainer;
566
chartContainer = _;
567
return nvtooltip;
568
};
569
570
nvtooltip.position = function(_) {
571
if (!arguments.length) return position;
572
position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
573
position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
574
return nvtooltip;
575
};
576
577
nvtooltip.fixedTop = function(_) {
578
if (!arguments.length) return fixedTop;
579
fixedTop = _;
580
return nvtooltip;
581
};
582
583
nvtooltip.enabled = function(_) {
584
if (!arguments.length) return enabled;
585
enabled = _;
586
return nvtooltip;
587
};
588
589
nvtooltip.valueFormatter = function(_) {
590
if (!arguments.length) return valueFormatter;
591
if (typeof _ === 'function') {
592
valueFormatter = _;
593
}
594
return nvtooltip;
595
};
596
597
nvtooltip.headerFormatter = function(_) {
598
if (!arguments.length) return headerFormatter;
599
if (typeof _ === 'function') {
600
headerFormatter = _;
601
}
602
return nvtooltip;
603
};
604
605
//id() is a read-only function. You can't use it to set the id.
606
nvtooltip.id = function() {
607
return id;
608
};
609
610
611
return nvtooltip;
612
};
613
614
615
//Original tooltip.show function. Kept for backward compatibility.
616
// pos = [left,top]
617
nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
618
619
//Create new tooltip div if it doesn't exist on DOM.
620
var container = document.createElement('div');
621
container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
622
623
var body = parentContainer;
624
if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
625
//If the parent element is an SVG element, place tooltip in the <body> element.
626
body = document.getElementsByTagName('body')[0];
627
}
628
629
container.style.left = 0;
630
container.style.top = 0;
631
container.style.opacity = 0;
632
container.innerHTML = content;
633
body.appendChild(container);
634
635
//If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
636
if (parentContainer) {
637
pos[0] = pos[0] - parentContainer.scrollLeft;
638
pos[1] = pos[1] - parentContainer.scrollTop;
639
}
640
nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
641
};
642
643
//Looks up the ancestry of a DOM element, and returns the first NON-svg node.
644
nv.tooltip.findFirstNonSVGParent = function(Elem) {
645
while(Elem.tagName.match(/^g|svg$/i) !== null) {
646
Elem = Elem.parentNode;
647
}
648
return Elem;
649
};
650
651
//Finds the total offsetTop of a given DOM element.
652
//Looks up the entire ancestry of an element, up to the first relatively positioned element.
653
nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
654
var offsetTop = initialTop;
655
656
do {
657
if( !isNaN( Elem.offsetTop ) ) {
658
offsetTop += (Elem.offsetTop);
659
}
660
} while( Elem = Elem.offsetParent );
661
return offsetTop;
662
};
663
664
//Finds the total offsetLeft of a given DOM element.
665
//Looks up the entire ancestry of an element, up to the first relatively positioned element.
666
nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
667
var offsetLeft = initialLeft;
668
669
do {
670
if( !isNaN( Elem.offsetLeft ) ) {
671
offsetLeft += (Elem.offsetLeft);
672
}
673
} while( Elem = Elem.offsetParent );
674
return offsetLeft;
675
};
676
677
//Global utility function to render a tooltip on the DOM.
678
//pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
679
//gravity = how to orient the tooltip
680
//dist = how far away from the mouse to place tooltip
681
//container = tooltip DIV
682
nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
683
684
var height = parseInt(container.offsetHeight),
685
width = parseInt(container.offsetWidth),
686
windowWidth = nv.utils.windowSize().width,
687
windowHeight = nv.utils.windowSize().height,
688
scrollTop = window.pageYOffset,
689
scrollLeft = window.pageXOffset,
690
left, top;
691
692
windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
693
windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
694
695
gravity = gravity || 's';
696
dist = dist || 20;
697
698
var tooltipTop = function ( Elem ) {
699
return nv.tooltip.findTotalOffsetTop(Elem, top);
700
};
701
702
var tooltipLeft = function ( Elem ) {
703
return nv.tooltip.findTotalOffsetLeft(Elem,left);
704
};
705
706
switch (gravity) {
707
case 'e':
708
left = pos[0] - width - dist;
709
top = pos[1] - (height / 2);
710
var tLeft = tooltipLeft(container);
711
var tTop = tooltipTop(container);
712
if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
713
if (tTop < scrollTop) top = scrollTop - tTop + top;
714
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
715
break;
716
case 'w':
717
left = pos[0] + dist;
718
top = pos[1] - (height / 2);
719
var tLeft = tooltipLeft(container);
720
var tTop = tooltipTop(container);
721
if (tLeft + width > windowWidth) left = pos[0] - width - dist;
722
if (tTop < scrollTop) top = scrollTop + 5;
723
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
724
break;
725
case 'n':
726
left = pos[0] - (width / 2) - 5;
727
top = pos[1] + dist;
728
var tLeft = tooltipLeft(container);
729
var tTop = tooltipTop(container);
730
if (tLeft < scrollLeft) left = scrollLeft + 5;
731
if (tLeft + width > windowWidth) left = left - width/2 + 5;
732
if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
733
break;
734
case 's':
735
left = pos[0] - (width / 2);
736
top = pos[1] - height - dist;
737
var tLeft = tooltipLeft(container);
738
var tTop = tooltipTop(container);
739
if (tLeft < scrollLeft) left = scrollLeft + 5;
740
if (tLeft + width > windowWidth) left = left - width/2 + 5;
741
if (scrollTop > tTop) top = scrollTop;
742
break;
743
case 'none':
744
left = pos[0];
745
top = pos[1] - dist;
746
var tLeft = tooltipLeft(container);
747
var tTop = tooltipTop(container);
748
break;
749
}
750
751
752
container.style.left = left+'px';
753
container.style.top = top+'px';
754
container.style.opacity = 1;
755
container.style.position = 'absolute';
756
757
return container;
758
};
759
760
//Global utility function to remove tooltips from the DOM.
761
nv.tooltip.cleanup = function() {
762
763
// Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
764
var tooltips = document.getElementsByClassName('nvtooltip');
765
var purging = [];
766
while(tooltips.length) {
767
purging.push(tooltips[0]);
768
tooltips[0].style.transitionDelay = '0 !important';
769
tooltips[0].style.opacity = 0;
770
tooltips[0].className = 'nvtooltip-pending-removal';
771
}
772
773
setTimeout(function() {
774
775
while (purging.length) {
776
var removeMe = purging.pop();
777
removeMe.parentNode.removeChild(removeMe);
778
}
779
}, 500);
780
};
781
782
})();
783
784
nv.utils.windowSize = function() {
785
// Sane defaults
786
var size = {width: 640, height: 480};
787
788
// Earlier IE uses Doc.body
789
if (document.body && document.body.offsetWidth) {
790
size.width = document.body.offsetWidth;
791
size.height = document.body.offsetHeight;
792
}
793
794
// IE can use depending on mode it is in
795
if (document.compatMode=='CSS1Compat' &&
796
document.documentElement &&
797
document.documentElement.offsetWidth ) {
798
size.width = document.documentElement.offsetWidth;
799
size.height = document.documentElement.offsetHeight;
800
}
801
802
// Most recent browsers use
803
if (window.innerWidth && window.innerHeight) {
804
size.width = window.innerWidth;
805
size.height = window.innerHeight;
806
}
807
return (size);
808
};
809
810
811
812
// Easy way to bind multiple functions to window.onresize
813
// TODO: give a way to remove a function after its bound, other than removing all of them
814
nv.utils.windowResize = function(fun){
815
if (fun === undefined) return;
816
var oldresize = window.onresize;
817
818
window.onresize = function(e) {
819
if (typeof oldresize == 'function') oldresize(e);
820
fun(e);
821
}
822
}
823
824
// Backwards compatible way to implement more d3-like coloring of graphs.
825
// If passed an array, wrap it in a function which implements the old default
826
// behavior
827
nv.utils.getColor = function(color) {
828
if (!arguments.length) return nv.utils.defaultColor(); //if you pass in nothing, get default colors back
829
830
if( Object.prototype.toString.call( color ) === '[object Array]' )
831
return function(d, i) { return d.color || color[i % color.length]; };
832
else
833
return color;
834
//can't really help it if someone passes rubbish as color
835
}
836
837
// Default color chooser uses the index of an object as before.
838
nv.utils.defaultColor = function() {
839
var colors = d3.scale.category20().range();
840
return function(d, i) { return d.color || colors[i % colors.length] };
841
}
842
843
844
// Returns a color function that takes the result of 'getKey' for each series and
845
// looks for a corresponding color from the dictionary,
846
nv.utils.customTheme = function(dictionary, getKey, defaultColors) {
847
getKey = getKey || function(series) { return series.key }; // use default series.key if getKey is undefined
848
defaultColors = defaultColors || d3.scale.category20().range(); //default color function
849
850
var defIndex = defaultColors.length; //current default color (going in reverse)
851
852
return function(series, index) {
853
var key = getKey(series);
854
855
if (!defIndex) defIndex = defaultColors.length; //used all the default colors, start over
856
857
if (typeof dictionary[key] !== "undefined")
858
return (typeof dictionary[key] === "function") ? dictionary[key]() : dictionary[key];
859
else
860
return defaultColors[--defIndex]; // no match in dictionary, use default color
861
}
862
}
863
864
865
866
// From the PJAX example on d3js.org, while this is not really directly needed
867
// it's a very cool method for doing pjax, I may expand upon it a little bit,
868
// open to suggestions on anything that may be useful
869
nv.utils.pjax = function(links, content) {
870
d3.selectAll(links).on("click", function() {
871
history.pushState(this.href, this.textContent, this.href);
872
load(this.href);
873
d3.event.preventDefault();
874
});
875
876
function load(href) {
877
d3.html(href, function(fragment) {
878
var target = d3.select(content).node();
879
target.parentNode.replaceChild(d3.select(fragment).select(content).node(), target);
880
nv.utils.pjax(links, content);
881
});
882
}
883
884
d3.select(window).on("popstate", function() {
885
if (d3.event.state) load(d3.event.state);
886
});
887
}
888
889
/* For situations where we want to approximate the width in pixels for an SVG:text element.
890
Most common instance is when the element is in a display:none; container.
891
Forumla is : text.length * font-size * constant_factor
892
*/
893
nv.utils.calcApproxTextWidth = function (svgTextElem) {
894
if (svgTextElem instanceof d3.selection) {
895
var fontSize = parseInt(svgTextElem.style("font-size").replace("px",""));
896
var textLength = svgTextElem.text().length;
897
898
return textLength * fontSize * 0.5;
899
}
900
return 0;
901
};
902
903
/* Numbers that are undefined, null or NaN, convert them to zeros.
904
*/
905
nv.utils.NaNtoZero = function(n) {
906
if (typeof n !== 'number'
907
|| isNaN(n)
908
|| n === null
909
|| n === Infinity) return 0;
910
911
return n;
912
};
913
914
/*
915
Snippet of code you can insert into each nv.models.* to give you the ability to
916
do things like:
917
chart.options({
918
showXAxis: true,
919
tooltips: true
920
});
921
922
To enable in the chart:
923
chart.options = nv.utils.optionsFunc.bind(chart);
924
*/
925
nv.utils.optionsFunc = function(args) {
926
if (args) {
927
d3.map(args).forEach((function(key,value) {
928
if (typeof this[key] === "function") {
929
this[key](value);
930
}
931
}).bind(this));
932
}
933
return this;
934
};nv.models.axis = function() {
935
"use strict";
936
//============================================================
937
// Public Variables with Default Settings
938
//------------------------------------------------------------
939
940
var axis = d3.svg.axis()
941
;
942
943
var margin = {top: 0, right: 0, bottom: 0, left: 0}
944
, width = 75 //only used for tickLabel currently
945
, height = 60 //only used for tickLabel currently
946
, scale = d3.scale.linear()
947
, axisLabelText = null
948
, showMaxMin = true //TODO: showMaxMin should be disabled on all ordinal scaled axes
949
, highlightZero = true
950
, rotateLabels = 0
951
, rotateYLabel = true
952
, staggerLabels = false
953
, isOrdinal = false
954
, ticks = null
955
;
956
957
axis
958
.scale(scale)
959
.orient('bottom')
960
.tickFormat(function(d) { return d })
961
;
962
963
//============================================================
964
965
966
//============================================================
967
// Private Variables
968
//------------------------------------------------------------
969
970
var scale0;
971
972
//============================================================
973
974
975
function chart(selection) {
976
selection.each(function(data) {
977
var container = d3.select(this);
978
979
980
//------------------------------------------------------------
981
// Setup containers and skeleton of chart
982
983
var wrap = container.selectAll('g.nv-wrap.nv-axis').data([data]);
984
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-axis');
985
var gEnter = wrapEnter.append('g');
986
var g = wrap.select('g')
987
988
//------------------------------------------------------------
989
990
991
if (ticks !== null)
992
axis.ticks(ticks);
993
else if (axis.orient() == 'top' || axis.orient() == 'bottom')
994
axis.ticks(Math.abs(scale.range()[1] - scale.range()[0]) / 100);
995
996
997
//TODO: consider calculating width/height based on whether or not label is added, for reference in charts using this component
998
999
1000
g.transition().call(axis);
1001
1002
scale0 = scale0 || axis.scale();
1003
1004
var fmt = axis.tickFormat();
1005
if (fmt == null) {
1006
fmt = scale0.tickFormat();
1007
}
1008
1009
var axisLabel = g.selectAll('text.nv-axislabel')
1010
.data([axisLabelText || null]);
1011
axisLabel.exit().remove();
1012
switch (axis.orient()) {
1013
case 'top':
1014
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1015
var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1016
axisLabel
1017
.attr('text-anchor', 'middle')
1018
.attr('y', 0)
1019
.attr('x', w/2);
1020
if (showMaxMin) {
1021
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1022
.data(scale.domain());
1023
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1024
axisMaxMin.exit().remove();
1025
axisMaxMin
1026
.attr('transform', function(d,i) {
1027
return 'translate(' + scale(d) + ',0)'
1028
})
1029
.select('text')
1030
.attr('dy', '0em')
1031
.attr('y', -axis.tickPadding())
1032
.attr('text-anchor', 'middle')
1033
.text(function(d,i) {
1034
var v = fmt(d);
1035
return ('' + v).match('NaN') ? '' : v;
1036
});
1037
axisMaxMin.transition()
1038
.attr('transform', function(d,i) {
1039
return 'translate(' + scale.range()[i] + ',0)'
1040
});
1041
}
1042
break;
1043
case 'bottom':
1044
var xLabelMargin = 36;
1045
var maxTextWidth = 30;
1046
var xTicks = g.selectAll('g').select("text");
1047
if (rotateLabels%360) {
1048
//Calculate the longest xTick width
1049
xTicks.each(function(d,i){
1050
var width = this.getBBox().width;
1051
if(width > maxTextWidth) maxTextWidth = width;
1052
});
1053
//Convert to radians before calculating sin. Add 30 to margin for healthy padding.
1054
var sin = Math.abs(Math.sin(rotateLabels*Math.PI/180));
1055
var xLabelMargin = (sin ? sin*maxTextWidth : maxTextWidth)+30;
1056
//Rotate all xTicks
1057
xTicks
1058
.attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1059
.style('text-anchor', rotateLabels%360 > 0 ? 'start' : 'end');
1060
}
1061
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1062
var w = (scale.range().length==2) ? scale.range()[1] : (scale.range()[scale.range().length-1]+(scale.range()[1]-scale.range()[0]));
1063
axisLabel
1064
.attr('text-anchor', 'middle')
1065
.attr('y', xLabelMargin)
1066
.attr('x', w/2);
1067
if (showMaxMin) {
1068
//if (showMaxMin && !isOrdinal) {
1069
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1070
//.data(scale.domain())
1071
.data([scale.domain()[0], scale.domain()[scale.domain().length - 1]]);
1072
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text');
1073
axisMaxMin.exit().remove();
1074
axisMaxMin
1075
.attr('transform', function(d,i) {
1076
return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1077
})
1078
.select('text')
1079
.attr('dy', '.71em')
1080
.attr('y', axis.tickPadding())
1081
.attr('transform', function(d,i,j) { return 'rotate(' + rotateLabels + ' 0,0)' })
1082
.style('text-anchor', rotateLabels ? (rotateLabels%360 > 0 ? 'start' : 'end') : 'middle')
1083
.text(function(d,i) {
1084
var v = fmt(d);
1085
return ('' + v).match('NaN') ? '' : v;
1086
});
1087
axisMaxMin.transition()
1088
.attr('transform', function(d,i) {
1089
//return 'translate(' + scale.range()[i] + ',0)'
1090
//return 'translate(' + scale(d) + ',0)'
1091
return 'translate(' + (scale(d) + (isOrdinal ? scale.rangeBand() / 2 : 0)) + ',0)'
1092
});
1093
}
1094
if (staggerLabels)
1095
xTicks
1096
.attr('transform', function(d,i) { return 'translate(0,' + (i % 2 == 0 ? '0' : '12') + ')' });
1097
1098
break;
1099
case 'right':
1100
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1101
axisLabel
1102
.style('text-anchor', rotateYLabel ? 'middle' : 'begin')
1103
.attr('transform', rotateYLabel ? 'rotate(90)' : '')
1104
.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
1105
.attr('x', rotateYLabel ? (scale.range()[0] / 2) : axis.tickPadding());
1106
if (showMaxMin) {
1107
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1108
.data(scale.domain());
1109
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1110
.style('opacity', 0);
1111
axisMaxMin.exit().remove();
1112
axisMaxMin
1113
.attr('transform', function(d,i) {
1114
return 'translate(0,' + scale(d) + ')'
1115
})
1116
.select('text')
1117
.attr('dy', '.32em')
1118
.attr('y', 0)
1119
.attr('x', axis.tickPadding())
1120
.style('text-anchor', 'start')
1121
.text(function(d,i) {
1122
var v = fmt(d);
1123
return ('' + v).match('NaN') ? '' : v;
1124
});
1125
axisMaxMin.transition()
1126
.attr('transform', function(d,i) {
1127
return 'translate(0,' + scale.range()[i] + ')'
1128
})
1129
.select('text')
1130
.style('opacity', 1);
1131
}
1132
break;
1133
case 'left':
1134
/*
1135
//For dynamically placing the label. Can be used with dynamically-sized chart axis margins
1136
var yTicks = g.selectAll('g').select("text");
1137
yTicks.each(function(d,i){
1138
var labelPadding = this.getBBox().width + axis.tickPadding() + 16;
1139
if(labelPadding > width) width = labelPadding;
1140
});
1141
*/
1142
axisLabel.enter().append('text').attr('class', 'nv-axislabel');
1143
axisLabel
1144
.style('text-anchor', rotateYLabel ? 'middle' : 'end')
1145
.attr('transform', rotateYLabel ? 'rotate(-90)' : '')
1146
.attr('y', rotateYLabel ? (-Math.max(margin.left,width) + 12) : -10) //TODO: consider calculating this based on largest tick width... OR at least expose this on chart
1147
.attr('x', rotateYLabel ? (-scale.range()[0] / 2) : -axis.tickPadding());
1148
if (showMaxMin) {
1149
var axisMaxMin = wrap.selectAll('g.nv-axisMaxMin')
1150
.data(scale.domain());
1151
axisMaxMin.enter().append('g').attr('class', 'nv-axisMaxMin').append('text')
1152
.style('opacity', 0);
1153
axisMaxMin.exit().remove();
1154
axisMaxMin
1155
.attr('transform', function(d,i) {
1156
return 'translate(0,' + scale0(d) + ')'
1157
})
1158
.select('text')
1159
.attr('dy', '.32em')
1160
.attr('y', 0)
1161
.attr('x', -axis.tickPadding())
1162
.attr('text-anchor', 'end')
1163
.text(function(d,i) {
1164
var v = fmt(d);
1165
return ('' + v).match('NaN') ? '' : v;
1166
});
1167
axisMaxMin.transition()
1168
.attr('transform', function(d,i) {
1169
return 'translate(0,' + scale.range()[i] + ')'
1170
})
1171
.select('text')
1172
.style('opacity', 1);
1173
}
1174
break;
1175
}
1176
axisLabel
1177
.text(function(d) { return d });
1178
1179
1180
if (showMaxMin && (axis.orient() === 'left' || axis.orient() === 'right')) {
1181
//check if max and min overlap other values, if so, hide the values that overlap
1182
g.selectAll('g') // the g's wrapping each tick
1183
.each(function(d,i) {
1184
d3.select(this).select('text').attr('opacity', 1);
1185
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!
1186
if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1187
d3.select(this).attr('opacity', 0);
1188
1189
d3.select(this).select('text').attr('opacity', 0); // Don't remove the ZERO line!!
1190
}
1191
});
1192
1193
//if Max and Min = 0 only show min, Issue #281
1194
if (scale.domain()[0] == scale.domain()[1] && scale.domain()[0] == 0)
1195
wrap.selectAll('g.nv-axisMaxMin')
1196
.style('opacity', function(d,i) { return !i ? 1 : 0 });
1197
1198
}
1199
1200
if (showMaxMin && (axis.orient() === 'top' || axis.orient() === 'bottom')) {
1201
var maxMinRange = [];
1202
wrap.selectAll('g.nv-axisMaxMin')
1203
.each(function(d,i) {
1204
try {
1205
if (i) // i== 1, max position
1206
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)
1207
else // i==0, min position
1208
maxMinRange.push(scale(d) + this.getBBox().width + 4)
1209
}catch (err) {
1210
if (i) // i== 1, max position
1211
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)
1212
else // i==0, min position
1213
maxMinRange.push(scale(d) + 4)
1214
}
1215
});
1216
g.selectAll('g') // the g's wrapping each tick
1217
.each(function(d,i) {
1218
if (scale(d) < maxMinRange[0] || scale(d) > maxMinRange[1]) {
1219
if (d > 1e-10 || d < -1e-10) // accounts for minor floating point errors... though could be problematic if the scale is EXTREMELY SMALL
1220
d3.select(this).remove();
1221
else
1222
d3.select(this).select('text').remove(); // Don't remove the ZERO line!!
1223
}
1224
});
1225
}
1226
1227
1228
//highlight zero line ... Maybe should not be an option and should just be in CSS?
1229
if (highlightZero)
1230
g.selectAll('.tick')
1231
.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
1232
.classed('zero', true);
1233
1234
//store old scales for use in transitions on update
1235
scale0 = scale.copy();
1236
1237
});
1238
1239
return chart;
1240
}
1241
1242
1243
//============================================================
1244
// Expose Public Variables
1245
//------------------------------------------------------------
1246
1247
// expose chart's sub-components
1248
chart.axis = axis;
1249
1250
d3.rebind(chart, axis, 'orient', 'tickValues', 'tickSubdivide', 'tickSize', 'tickPadding', 'tickFormat');
1251
d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands'); //these are also accessible by chart.scale(), but added common ones directly for ease of use
1252
1253
chart.options = nv.utils.optionsFunc.bind(chart);
1254
1255
chart.margin = function(_) {
1256
if(!arguments.length) return margin;
1257
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1258
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1259
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1260
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1261
return chart;
1262
}
1263
1264
chart.width = function(_) {
1265
if (!arguments.length) return width;
1266
width = _;
1267
return chart;
1268
};
1269
1270
chart.ticks = function(_) {
1271
if (!arguments.length) return ticks;
1272
ticks = _;
1273
return chart;
1274
};
1275
1276
chart.height = function(_) {
1277
if (!arguments.length) return height;
1278
height = _;
1279
return chart;
1280
};
1281
1282
chart.axisLabel = function(_) {
1283
if (!arguments.length) return axisLabelText;
1284
axisLabelText = _;
1285
return chart;
1286
}
1287
1288
chart.showMaxMin = function(_) {
1289
if (!arguments.length) return showMaxMin;
1290
showMaxMin = _;
1291
return chart;
1292
}
1293
1294
chart.highlightZero = function(_) {
1295
if (!arguments.length) return highlightZero;
1296
highlightZero = _;
1297
return chart;
1298
}
1299
1300
chart.scale = function(_) {
1301
if (!arguments.length) return scale;
1302
scale = _;
1303
axis.scale(scale);
1304
isOrdinal = typeof scale.rangeBands === 'function';
1305
d3.rebind(chart, scale, 'domain', 'range', 'rangeBand', 'rangeBands');
1306
return chart;
1307
}
1308
1309
chart.rotateYLabel = function(_) {
1310
if(!arguments.length) return rotateYLabel;
1311
rotateYLabel = _;
1312
return chart;
1313
}
1314
1315
chart.rotateLabels = function(_) {
1316
if(!arguments.length) return rotateLabels;
1317
rotateLabels = _;
1318
return chart;
1319
}
1320
1321
chart.staggerLabels = function(_) {
1322
if (!arguments.length) return staggerLabels;
1323
staggerLabels = _;
1324
return chart;
1325
};
1326
1327
//============================================================
1328
1329
1330
return chart;
1331
}
1332
1333
// Chart design based on the recommendations of Stephen Few. Implementation
1334
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1335
// http://projects.instantcognition.com/protovis/bulletchart/
1336
1337
nv.models.bullet = function() {
1338
"use strict";
1339
//============================================================
1340
// Public Variables with Default Settings
1341
//------------------------------------------------------------
1342
1343
var margin = {top: 0, right: 0, bottom: 0, left: 0}
1344
, orient = 'left' // TODO top & bottom
1345
, reverse = false
1346
, ranges = function(d) { return d.ranges }
1347
, markers = function(d) { return d.markers }
1348
, measures = function(d) { return d.measures }
1349
, rangeLabels = function(d) { return d.rangeLabels ? d.rangeLabels : [] }
1350
, markerLabels = function(d) { return d.markerLabels ? d.markerLabels : [] }
1351
, measureLabels = function(d) { return d.measureLabels ? d.measureLabels : [] }
1352
, forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
1353
, width = 380
1354
, height = 30
1355
, tickFormat = null
1356
, color = nv.utils.getColor(['#1f77b4'])
1357
, dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
1358
;
1359
1360
//============================================================
1361
1362
1363
function chart(selection) {
1364
selection.each(function(d, i) {
1365
var availableWidth = width - margin.left - margin.right,
1366
availableHeight = height - margin.top - margin.bottom,
1367
container = d3.select(this);
1368
1369
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1370
markerz = markers.call(this, d, i).slice().sort(d3.descending),
1371
measurez = measures.call(this, d, i).slice().sort(d3.descending),
1372
rangeLabelz = rangeLabels.call(this, d, i).slice(),
1373
markerLabelz = markerLabels.call(this, d, i).slice(),
1374
measureLabelz = measureLabels.call(this, d, i).slice();
1375
1376
1377
//------------------------------------------------------------
1378
// Setup Scales
1379
1380
// Compute the new x-scale.
1381
var x1 = d3.scale.linear()
1382
.domain( d3.extent(d3.merge([forceX, rangez])) )
1383
.range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1384
1385
// Retrieve the old x-scale, if this is an update.
1386
var x0 = this.__chart__ || d3.scale.linear()
1387
.domain([0, Infinity])
1388
.range(x1.range());
1389
1390
// Stash the new scale.
1391
this.__chart__ = x1;
1392
1393
1394
var rangeMin = d3.min(rangez), //rangez[2]
1395
rangeMax = d3.max(rangez), //rangez[0]
1396
rangeAvg = rangez[1];
1397
1398
//------------------------------------------------------------
1399
1400
1401
//------------------------------------------------------------
1402
// Setup containers and skeleton of chart
1403
1404
var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
1405
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
1406
var gEnter = wrapEnter.append('g');
1407
var g = wrap.select('g');
1408
1409
gEnter.append('rect').attr('class', 'nv-range nv-rangeMax');
1410
gEnter.append('rect').attr('class', 'nv-range nv-rangeAvg');
1411
gEnter.append('rect').attr('class', 'nv-range nv-rangeMin');
1412
gEnter.append('rect').attr('class', 'nv-measure');
1413
gEnter.append('path').attr('class', 'nv-markerTriangle');
1414
1415
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1416
1417
//------------------------------------------------------------
1418
1419
1420
1421
var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1422
w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1423
var xp0 = function(d) { return d < 0 ? x0(d) : x0(0) },
1424
xp1 = function(d) { return d < 0 ? x1(d) : x1(0) };
1425
1426
1427
g.select('rect.nv-rangeMax')
1428
.attr('height', availableHeight)
1429
.attr('width', w1(rangeMax > 0 ? rangeMax : rangeMin))
1430
.attr('x', xp1(rangeMax > 0 ? rangeMax : rangeMin))
1431
.datum(rangeMax > 0 ? rangeMax : rangeMin)
1432
/*
1433
.attr('x', rangeMin < 0 ?
1434
rangeMax > 0 ?
1435
x1(rangeMin)
1436
: x1(rangeMax)
1437
: x1(0))
1438
*/
1439
1440
g.select('rect.nv-rangeAvg')
1441
.attr('height', availableHeight)
1442
.attr('width', w1(rangeAvg))
1443
.attr('x', xp1(rangeAvg))
1444
.datum(rangeAvg)
1445
/*
1446
.attr('width', rangeMax <= 0 ?
1447
x1(rangeMax) - x1(rangeAvg)
1448
: x1(rangeAvg) - x1(rangeMin))
1449
.attr('x', rangeMax <= 0 ?
1450
x1(rangeAvg)
1451
: x1(rangeMin))
1452
*/
1453
1454
g.select('rect.nv-rangeMin')
1455
.attr('height', availableHeight)
1456
.attr('width', w1(rangeMax))
1457
.attr('x', xp1(rangeMax))
1458
.attr('width', w1(rangeMax > 0 ? rangeMin : rangeMax))
1459
.attr('x', xp1(rangeMax > 0 ? rangeMin : rangeMax))
1460
.datum(rangeMax > 0 ? rangeMin : rangeMax)
1461
/*
1462
.attr('width', rangeMax <= 0 ?
1463
x1(rangeAvg) - x1(rangeMin)
1464
: x1(rangeMax) - x1(rangeAvg))
1465
.attr('x', rangeMax <= 0 ?
1466
x1(rangeMin)
1467
: x1(rangeAvg))
1468
*/
1469
1470
g.select('rect.nv-measure')
1471
.style('fill', color)
1472
.attr('height', availableHeight / 3)
1473
.attr('y', availableHeight / 3)
1474
.attr('width', measurez < 0 ?
1475
x1(0) - x1(measurez[0])
1476
: x1(measurez[0]) - x1(0))
1477
.attr('x', xp1(measurez))
1478
.on('mouseover', function() {
1479
dispatch.elementMouseover({
1480
value: measurez[0],
1481
label: measureLabelz[0] || 'Current',
1482
pos: [x1(measurez[0]), availableHeight/2]
1483
})
1484
})
1485
.on('mouseout', function() {
1486
dispatch.elementMouseout({
1487
value: measurez[0],
1488
label: measureLabelz[0] || 'Current'
1489
})
1490
})
1491
1492
var h3 = availableHeight / 6;
1493
if (markerz[0]) {
1494
g.selectAll('path.nv-markerTriangle')
1495
.attr('transform', function(d) { return 'translate(' + x1(markerz[0]) + ',' + (availableHeight / 2) + ')' })
1496
.attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1497
.on('mouseover', function() {
1498
dispatch.elementMouseover({
1499
value: markerz[0],
1500
label: markerLabelz[0] || 'Previous',
1501
pos: [x1(markerz[0]), availableHeight/2]
1502
})
1503
})
1504
.on('mouseout', function() {
1505
dispatch.elementMouseout({
1506
value: markerz[0],
1507
label: markerLabelz[0] || 'Previous'
1508
})
1509
});
1510
} else {
1511
g.selectAll('path.nv-markerTriangle').remove();
1512
}
1513
1514
1515
wrap.selectAll('.nv-range')
1516
.on('mouseover', function(d,i) {
1517
var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1518
1519
dispatch.elementMouseover({
1520
value: d,
1521
label: label,
1522
pos: [x1(d), availableHeight/2]
1523
})
1524
})
1525
.on('mouseout', function(d,i) {
1526
var label = rangeLabelz[i] || (!i ? "Maximum" : i == 1 ? "Mean" : "Minimum");
1527
1528
dispatch.elementMouseout({
1529
value: d,
1530
label: label
1531
})
1532
})
1533
1534
/* // THIS IS THE PREVIOUS BULLET IMPLEMENTATION, WILL REMOVE SHORTLY
1535
// Update the range rects.
1536
var range = g.selectAll('rect.nv-range')
1537
.data(rangez);
1538
1539
range.enter().append('rect')
1540
.attr('class', function(d, i) { return 'nv-range nv-s' + i; })
1541
.attr('width', w0)
1542
.attr('height', availableHeight)
1543
.attr('x', reverse ? x0 : 0)
1544
.on('mouseover', function(d,i) {
1545
dispatch.elementMouseover({
1546
value: d,
1547
label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
1548
pos: [x1(d), availableHeight/2]
1549
})
1550
})
1551
.on('mouseout', function(d,i) {
1552
dispatch.elementMouseout({
1553
value: d,
1554
label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
1555
})
1556
})
1557
1558
d3.transition(range)
1559
.attr('x', reverse ? x1 : 0)
1560
.attr('width', w1)
1561
.attr('height', availableHeight);
1562
1563
1564
// Update the measure rects.
1565
var measure = g.selectAll('rect.nv-measure')
1566
.data(measurez);
1567
1568
measure.enter().append('rect')
1569
.attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
1570
.style('fill', function(d,i) { return color(d,i ) })
1571
.attr('width', w0)
1572
.attr('height', availableHeight / 3)
1573
.attr('x', reverse ? x0 : 0)
1574
.attr('y', availableHeight / 3)
1575
.on('mouseover', function(d) {
1576
dispatch.elementMouseover({
1577
value: d,
1578
label: 'Current', //TODO: make these labels a variable
1579
pos: [x1(d), availableHeight/2]
1580
})
1581
})
1582
.on('mouseout', function(d) {
1583
dispatch.elementMouseout({
1584
value: d,
1585
label: 'Current' //TODO: make these labels a variable
1586
})
1587
})
1588
1589
d3.transition(measure)
1590
.attr('width', w1)
1591
.attr('height', availableHeight / 3)
1592
.attr('x', reverse ? x1 : 0)
1593
.attr('y', availableHeight / 3);
1594
1595
1596
1597
// Update the marker lines.
1598
var marker = g.selectAll('path.nv-markerTriangle')
1599
.data(markerz);
1600
1601
var h3 = availableHeight / 6;
1602
marker.enter().append('path')
1603
.attr('class', 'nv-markerTriangle')
1604
.attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
1605
.attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
1606
.on('mouseover', function(d,i) {
1607
dispatch.elementMouseover({
1608
value: d,
1609
label: 'Previous',
1610
pos: [x1(d), availableHeight/2]
1611
})
1612
})
1613
.on('mouseout', function(d,i) {
1614
dispatch.elementMouseout({
1615
value: d,
1616
label: 'Previous'
1617
})
1618
});
1619
1620
d3.transition(marker)
1621
.attr('transform', function(d) { return 'translate(' + (x1(d) - x1(0)) + ',' + (availableHeight / 2) + ')' });
1622
1623
marker.exit().remove();
1624
*/
1625
1626
});
1627
1628
// d3.timer.flush(); // Not needed?
1629
1630
return chart;
1631
}
1632
1633
1634
//============================================================
1635
// Expose Public Variables
1636
//------------------------------------------------------------
1637
1638
chart.dispatch = dispatch;
1639
1640
chart.options = nv.utils.optionsFunc.bind(chart);
1641
1642
// left, right, top, bottom
1643
chart.orient = function(_) {
1644
if (!arguments.length) return orient;
1645
orient = _;
1646
reverse = orient == 'right' || orient == 'bottom';
1647
return chart;
1648
};
1649
1650
// ranges (bad, satisfactory, good)
1651
chart.ranges = function(_) {
1652
if (!arguments.length) return ranges;
1653
ranges = _;
1654
return chart;
1655
};
1656
1657
// markers (previous, goal)
1658
chart.markers = function(_) {
1659
if (!arguments.length) return markers;
1660
markers = _;
1661
return chart;
1662
};
1663
1664
// measures (actual, forecast)
1665
chart.measures = function(_) {
1666
if (!arguments.length) return measures;
1667
measures = _;
1668
return chart;
1669
};
1670
1671
chart.forceX = function(_) {
1672
if (!arguments.length) return forceX;
1673
forceX = _;
1674
return chart;
1675
};
1676
1677
chart.width = function(_) {
1678
if (!arguments.length) return width;
1679
width = _;
1680
return chart;
1681
};
1682
1683
chart.height = function(_) {
1684
if (!arguments.length) return height;
1685
height = _;
1686
return chart;
1687
};
1688
1689
chart.margin = function(_) {
1690
if (!arguments.length) return margin;
1691
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
1692
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
1693
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
1694
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
1695
return chart;
1696
};
1697
1698
chart.tickFormat = function(_) {
1699
if (!arguments.length) return tickFormat;
1700
tickFormat = _;
1701
return chart;
1702
};
1703
1704
chart.color = function(_) {
1705
if (!arguments.length) return color;
1706
color = nv.utils.getColor(_);
1707
return chart;
1708
};
1709
1710
//============================================================
1711
1712
1713
return chart;
1714
};
1715
1716
1717
1718
// Chart design based on the recommendations of Stephen Few. Implementation
1719
// based on the work of Clint Ivy, Jamie Love, and Jason Davies.
1720
// http://projects.instantcognition.com/protovis/bulletchart/
1721
nv.models.bulletChart = function() {
1722
"use strict";
1723
//============================================================
1724
// Public Variables with Default Settings
1725
//------------------------------------------------------------
1726
1727
var bullet = nv.models.bullet()
1728
;
1729
1730
var orient = 'left' // TODO top & bottom
1731
, reverse = false
1732
, margin = {top: 5, right: 40, bottom: 20, left: 120}
1733
, ranges = function(d) { return d.ranges }
1734
, markers = function(d) { return d.markers }
1735
, measures = function(d) { return d.measures }
1736
, width = null
1737
, height = 55
1738
, tickFormat = null
1739
, tooltips = true
1740
, tooltip = function(key, x, y, e, graph) {
1741
return '<h3>' + x + '</h3>' +
1742
'<p>' + y + '</p>'
1743
}
1744
, noData = 'No Data Available.'
1745
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide')
1746
;
1747
1748
//============================================================
1749
1750
1751
//============================================================
1752
// Private Variables
1753
//------------------------------------------------------------
1754
1755
var showTooltip = function(e, offsetElement) {
1756
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ) + margin.left,
1757
top = e.pos[1] + ( offsetElement.offsetTop || 0) + margin.top,
1758
content = tooltip(e.key, e.label, e.value, e, chart);
1759
1760
nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
1761
};
1762
1763
//============================================================
1764
1765
1766
function chart(selection) {
1767
selection.each(function(d, i) {
1768
var container = d3.select(this);
1769
1770
var availableWidth = (width || parseInt(container.style('width')) || 960)
1771
- margin.left - margin.right,
1772
availableHeight = height - margin.top - margin.bottom,
1773
that = this;
1774
1775
1776
chart.update = function() { chart(selection) };
1777
chart.container = this;
1778
1779
//------------------------------------------------------------
1780
// Display No Data message if there's nothing to show.
1781
1782
if (!d || !ranges.call(this, d, i)) {
1783
var noDataText = container.selectAll('.nv-noData').data([noData]);
1784
1785
noDataText.enter().append('text')
1786
.attr('class', 'nvd3 nv-noData')
1787
.attr('dy', '-.7em')
1788
.style('text-anchor', 'middle');
1789
1790
noDataText
1791
.attr('x', margin.left + availableWidth / 2)
1792
.attr('y', 18 + margin.top + availableHeight / 2)
1793
.text(function(d) { return d });
1794
1795
return chart;
1796
} else {
1797
container.selectAll('.nv-noData').remove();
1798
}
1799
1800
//------------------------------------------------------------
1801
1802
1803
1804
var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
1805
markerz = markers.call(this, d, i).slice().sort(d3.descending),
1806
measurez = measures.call(this, d, i).slice().sort(d3.descending);
1807
1808
1809
//------------------------------------------------------------
1810
// Setup containers and skeleton of chart
1811
1812
var wrap = container.selectAll('g.nv-wrap.nv-bulletChart').data([d]);
1813
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bulletChart');
1814
var gEnter = wrapEnter.append('g');
1815
var g = wrap.select('g');
1816
1817
gEnter.append('g').attr('class', 'nv-bulletWrap');
1818
gEnter.append('g').attr('class', 'nv-titles');
1819
1820
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
1821
1822
//------------------------------------------------------------
1823
1824
1825
// Compute the new x-scale.
1826
var x1 = d3.scale.linear()
1827
.domain([0, Math.max(rangez[0], markerz[0], measurez[0])]) // TODO: need to allow forceX and forceY, and xDomain, yDomain
1828
.range(reverse ? [availableWidth, 0] : [0, availableWidth]);
1829
1830
// Retrieve the old x-scale, if this is an update.
1831
var x0 = this.__chart__ || d3.scale.linear()
1832
.domain([0, Infinity])
1833
.range(x1.range());
1834
1835
// Stash the new scale.
1836
this.__chart__ = x1;
1837
1838
/*
1839
// Derive width-scales from the x-scales.
1840
var w0 = bulletWidth(x0),
1841
w1 = bulletWidth(x1);
1842
1843
function bulletWidth(x) {
1844
var x0 = x(0);
1845
return function(d) {
1846
return Math.abs(x(d) - x(0));
1847
};
1848
}
1849
1850
function bulletTranslate(x) {
1851
return function(d) {
1852
return 'translate(' + x(d) + ',0)';
1853
};
1854
}
1855
*/
1856
1857
var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
1858
w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
1859
1860
1861
var title = gEnter.select('.nv-titles').append('g')
1862
.attr('text-anchor', 'end')
1863
.attr('transform', 'translate(-6,' + (height - margin.top - margin.bottom) / 2 + ')');
1864
title.append('text')
1865
.attr('class', 'nv-title')
1866
.text(function(d) { return d.title; });
1867
1868
title.append('text')
1869
.attr('class', 'nv-subtitle')
1870
.attr('dy', '1em')
1871
.text(function(d) { return d.subtitle; });
1872
1873
1874
1875
bullet
1876
.width(availableWidth)
1877
.height(availableHeight)
1878
1879
var bulletWrap = g.select('.nv-bulletWrap');
1880
1881
d3.transition(bulletWrap).call(bullet);
1882
1883
1884
1885
// Compute the tick format.
1886
var format = tickFormat || x1.tickFormat( availableWidth / 100 );
1887
1888
// Update the tick groups.
1889
var tick = g.selectAll('g.nv-tick')
1890
.data(x1.ticks( availableWidth / 50 ), function(d) {
1891
return this.textContent || format(d);
1892
});
1893
1894
// Initialize the ticks with the old scale, x0.
1895
var tickEnter = tick.enter().append('g')
1896
.attr('class', 'nv-tick')
1897
.attr('transform', function(d) { return 'translate(' + x0(d) + ',0)' })
1898
.style('opacity', 1e-6);
1899
1900
tickEnter.append('line')
1901
.attr('y1', availableHeight)
1902
.attr('y2', availableHeight * 7 / 6);
1903
1904
tickEnter.append('text')
1905
.attr('text-anchor', 'middle')
1906
.attr('dy', '1em')
1907
.attr('y', availableHeight * 7 / 6)
1908
.text(format);
1909
1910
1911
// Transition the updating ticks to the new scale, x1.
1912
var tickUpdate = d3.transition(tick)
1913
.attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1914
.style('opacity', 1);
1915
1916
tickUpdate.select('line')
1917
.attr('y1', availableHeight)
1918
.attr('y2', availableHeight * 7 / 6);
1919
1920
tickUpdate.select('text')
1921
.attr('y', availableHeight * 7 / 6);
1922
1923
// Transition the exiting ticks to the new scale, x1.
1924
d3.transition(tick.exit())
1925
.attr('transform', function(d) { return 'translate(' + x1(d) + ',0)' })
1926
.style('opacity', 1e-6)
1927
.remove();
1928
1929
1930
//============================================================
1931
// Event Handling/Dispatching (in chart's scope)
1932
//------------------------------------------------------------
1933
1934
dispatch.on('tooltipShow', function(e) {
1935
e.key = d.title;
1936
if (tooltips) showTooltip(e, that.parentNode);
1937
});
1938
1939
//============================================================
1940
1941
});
1942
1943
d3.timer.flush();
1944
1945
return chart;
1946
}
1947
1948
1949
//============================================================
1950
// Event Handling/Dispatching (out of chart's scope)
1951
//------------------------------------------------------------
1952
1953
bullet.dispatch.on('elementMouseover.tooltip', function(e) {
1954
dispatch.tooltipShow(e);
1955
});
1956
1957
bullet.dispatch.on('elementMouseout.tooltip', function(e) {
1958
dispatch.tooltipHide(e);
1959
});
1960
1961
dispatch.on('tooltipHide', function() {
1962
if (tooltips) nv.tooltip.cleanup();
1963
});
1964
1965
//============================================================
1966
1967
1968
//============================================================
1969
// Expose Public Variables
1970
//------------------------------------------------------------
1971
1972
chart.dispatch = dispatch;
1973
chart.bullet = bullet;
1974
1975
d3.rebind(chart, bullet, 'color');
1976
1977
chart.options = nv.utils.optionsFunc.bind(chart);
1978
1979
// left, right, top, bottom
1980
chart.orient = function(x) {
1981
if (!arguments.length) return orient;
1982
orient = x;
1983
reverse = orient == 'right' || orient == 'bottom';
1984
return chart;
1985
};
1986
1987
// ranges (bad, satisfactory, good)
1988
chart.ranges = function(x) {
1989
if (!arguments.length) return ranges;
1990
ranges = x;
1991
return chart;
1992
};
1993
1994
// markers (previous, goal)
1995
chart.markers = function(x) {
1996
if (!arguments.length) return markers;
1997
markers = x;
1998
return chart;
1999
};
2000
2001
// measures (actual, forecast)
2002
chart.measures = function(x) {
2003
if (!arguments.length) return measures;
2004
measures = x;
2005
return chart;
2006
};
2007
2008
chart.width = function(x) {
2009
if (!arguments.length) return width;
2010
width = x;
2011
return chart;
2012
};
2013
2014
chart.height = function(x) {
2015
if (!arguments.length) return height;
2016
height = x;
2017
return chart;
2018
};
2019
2020
chart.margin = function(_) {
2021
if (!arguments.length) return margin;
2022
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2023
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2024
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2025
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2026
return chart;
2027
};
2028
2029
chart.tickFormat = function(x) {
2030
if (!arguments.length) return tickFormat;
2031
tickFormat = x;
2032
return chart;
2033
};
2034
2035
chart.tooltips = function(_) {
2036
if (!arguments.length) return tooltips;
2037
tooltips = _;
2038
return chart;
2039
};
2040
2041
chart.tooltipContent = function(_) {
2042
if (!arguments.length) return tooltip;
2043
tooltip = _;
2044
return chart;
2045
};
2046
2047
chart.noData = function(_) {
2048
if (!arguments.length) return noData;
2049
noData = _;
2050
return chart;
2051
};
2052
2053
//============================================================
2054
2055
2056
return chart;
2057
};
2058
2059
2060
2061
nv.models.cumulativeLineChart = function() {
2062
"use strict";
2063
//============================================================
2064
// Public Variables with Default Settings
2065
//------------------------------------------------------------
2066
2067
var lines = nv.models.line()
2068
, xAxis = nv.models.axis()
2069
, yAxis = nv.models.axis()
2070
, legend = nv.models.legend()
2071
, controls = nv.models.legend()
2072
, interactiveLayer = nv.interactiveGuideline()
2073
;
2074
2075
var margin = {top: 30, right: 30, bottom: 50, left: 60}
2076
, color = nv.utils.defaultColor()
2077
, width = null
2078
, height = null
2079
, showLegend = true
2080
, showXAxis = true
2081
, showYAxis = true
2082
, rightAlignYAxis = false
2083
, tooltips = true
2084
, showControls = true
2085
, useInteractiveGuideline = false
2086
, rescaleY = true
2087
, tooltip = function(key, x, y, e, graph) {
2088
return '<h3>' + key + '</h3>' +
2089
'<p>' + y + ' at ' + x + '</p>'
2090
}
2091
, x //can be accessed via chart.xScale()
2092
, y //can be accessed via chart.yScale()
2093
, id = lines.id()
2094
, state = { index: 0, rescaleY: rescaleY }
2095
, defaultState = null
2096
, noData = 'No Data Available.'
2097
, average = function(d) { return d.average }
2098
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
2099
, transitionDuration = 250
2100
;
2101
2102
xAxis
2103
.orient('bottom')
2104
.tickPadding(7)
2105
;
2106
yAxis
2107
.orient((rightAlignYAxis) ? 'right' : 'left')
2108
;
2109
2110
//============================================================
2111
controls.updateState(false);
2112
2113
//============================================================
2114
// Private Variables
2115
//------------------------------------------------------------
2116
2117
var dx = d3.scale.linear()
2118
, index = {i: 0, x: 0}
2119
;
2120
2121
var showTooltip = function(e, offsetElement) {
2122
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
2123
top = e.pos[1] + ( offsetElement.offsetTop || 0),
2124
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
2125
y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
2126
content = tooltip(e.series.key, x, y, e, chart);
2127
2128
nv.tooltip.show([left, top], content, null, null, offsetElement);
2129
};
2130
2131
/*
2132
//Moved to see if we can get better behavior to fix issue #315
2133
var indexDrag = d3.behavior.drag()
2134
.on('dragstart', dragStart)
2135
.on('drag', dragMove)
2136
.on('dragend', dragEnd);
2137
2138
function dragStart(d,i) {
2139
d3.select(chart.container)
2140
.style('cursor', 'ew-resize');
2141
}
2142
2143
function dragMove(d,i) {
2144
d.x += d3.event.dx;
2145
d.i = Math.round(dx.invert(d.x));
2146
2147
d3.select(this).attr('transform', 'translate(' + dx(d.i) + ',0)');
2148
chart.update();
2149
}
2150
2151
function dragEnd(d,i) {
2152
d3.select(chart.container)
2153
.style('cursor', 'auto');
2154
chart.update();
2155
}
2156
*/
2157
2158
//============================================================
2159
2160
2161
function chart(selection) {
2162
selection.each(function(data) {
2163
var container = d3.select(this).classed('nv-chart-' + id, true),
2164
that = this;
2165
2166
var availableWidth = (width || parseInt(container.style('width')) || 960)
2167
- margin.left - margin.right,
2168
availableHeight = (height || parseInt(container.style('height')) || 400)
2169
- margin.top - margin.bottom;
2170
2171
2172
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
2173
chart.container = this;
2174
2175
//set state.disabled
2176
state.disabled = data.map(function(d) { return !!d.disabled });
2177
2178
if (!defaultState) {
2179
var key;
2180
defaultState = {};
2181
for (key in state) {
2182
if (state[key] instanceof Array)
2183
defaultState[key] = state[key].slice(0);
2184
else
2185
defaultState[key] = state[key];
2186
}
2187
}
2188
2189
var indexDrag = d3.behavior.drag()
2190
.on('dragstart', dragStart)
2191
.on('drag', dragMove)
2192
.on('dragend', dragEnd);
2193
2194
2195
function dragStart(d,i) {
2196
d3.select(chart.container)
2197
.style('cursor', 'ew-resize');
2198
}
2199
2200
function dragMove(d,i) {
2201
index.x = d3.event.x;
2202
index.i = Math.round(dx.invert(index.x));
2203
updateZero();
2204
}
2205
2206
function dragEnd(d,i) {
2207
d3.select(chart.container)
2208
.style('cursor', 'auto');
2209
2210
// update state and send stateChange with new index
2211
state.index = index.i;
2212
dispatch.stateChange(state);
2213
}
2214
2215
//------------------------------------------------------------
2216
// Display No Data message if there's nothing to show.
2217
2218
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
2219
var noDataText = container.selectAll('.nv-noData').data([noData]);
2220
2221
noDataText.enter().append('text')
2222
.attr('class', 'nvd3 nv-noData')
2223
.attr('dy', '-.7em')
2224
.style('text-anchor', 'middle');
2225
2226
noDataText
2227
.attr('x', margin.left + availableWidth / 2)
2228
.attr('y', margin.top + availableHeight / 2)
2229
.text(function(d) { return d });
2230
2231
return chart;
2232
} else {
2233
container.selectAll('.nv-noData').remove();
2234
}
2235
2236
//------------------------------------------------------------
2237
2238
2239
//------------------------------------------------------------
2240
// Setup Scales
2241
2242
x = lines.xScale();
2243
y = lines.yScale();
2244
2245
2246
if (!rescaleY) {
2247
var seriesDomains = data
2248
.filter(function(series) { return !series.disabled })
2249
.map(function(series,i) {
2250
var initialDomain = d3.extent(series.values, lines.y());
2251
2252
//account for series being disabled when losing 95% or more
2253
if (initialDomain[0] < -.95) initialDomain[0] = -.95;
2254
2255
return [
2256
(initialDomain[0] - initialDomain[1]) / (1 + initialDomain[1]),
2257
(initialDomain[1] - initialDomain[0]) / (1 + initialDomain[0])
2258
];
2259
});
2260
2261
var completeDomain = [
2262
d3.min(seriesDomains, function(d) { return d[0] }),
2263
d3.max(seriesDomains, function(d) { return d[1] })
2264
]
2265
2266
lines.yDomain(completeDomain);
2267
} else {
2268
lines.yDomain(null);
2269
}
2270
2271
2272
dx .domain([0, data[0].values.length - 1]) //Assumes all series have same length
2273
.range([0, availableWidth])
2274
.clamp(true);
2275
2276
//------------------------------------------------------------
2277
2278
2279
var data = indexify(index.i, data);
2280
2281
2282
//------------------------------------------------------------
2283
// Setup containers and skeleton of chart
2284
var interactivePointerEvents = (useInteractiveGuideline) ? "none" : "all";
2285
var wrap = container.selectAll('g.nv-wrap.nv-cumulativeLine').data([data]);
2286
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-cumulativeLine').append('g');
2287
var g = wrap.select('g');
2288
2289
gEnter.append('g').attr('class', 'nv-interactive');
2290
gEnter.append('g').attr('class', 'nv-x nv-axis').style("pointer-events","none");
2291
gEnter.append('g').attr('class', 'nv-y nv-axis');
2292
gEnter.append('g').attr('class', 'nv-background');
2293
gEnter.append('g').attr('class', 'nv-linesWrap').style("pointer-events",interactivePointerEvents);
2294
gEnter.append('g').attr('class', 'nv-avgLinesWrap').style("pointer-events","none");
2295
gEnter.append('g').attr('class', 'nv-legendWrap');
2296
gEnter.append('g').attr('class', 'nv-controlsWrap');
2297
2298
2299
//------------------------------------------------------------
2300
// Legend
2301
2302
if (showLegend) {
2303
legend.width(availableWidth);
2304
2305
g.select('.nv-legendWrap')
2306
.datum(data)
2307
.call(legend);
2308
2309
if ( margin.top != legend.height()) {
2310
margin.top = legend.height();
2311
availableHeight = (height || parseInt(container.style('height')) || 400)
2312
- margin.top - margin.bottom;
2313
}
2314
2315
g.select('.nv-legendWrap')
2316
.attr('transform', 'translate(0,' + (-margin.top) +')')
2317
}
2318
2319
//------------------------------------------------------------
2320
2321
2322
//------------------------------------------------------------
2323
// Controls
2324
2325
if (showControls) {
2326
var controlsData = [
2327
{ key: 'Re-scale y-axis', disabled: !rescaleY }
2328
];
2329
2330
controls.width(140).color(['#444', '#444', '#444']);
2331
g.select('.nv-controlsWrap')
2332
.datum(controlsData)
2333
.attr('transform', 'translate(0,' + (-margin.top) +')')
2334
.call(controls);
2335
}
2336
2337
//------------------------------------------------------------
2338
2339
2340
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2341
2342
if (rightAlignYAxis) {
2343
g.select(".nv-y.nv-axis")
2344
.attr("transform", "translate(" + availableWidth + ",0)");
2345
}
2346
2347
// Show error if series goes below 100%
2348
var tempDisabled = data.filter(function(d) { return d.tempDisabled });
2349
2350
wrap.select('.tempDisabled').remove(); //clean-up and prevent duplicates
2351
if (tempDisabled.length) {
2352
wrap.append('text').attr('class', 'tempDisabled')
2353
.attr('x', availableWidth / 2)
2354
.attr('y', '-.71em')
2355
.style('text-anchor', 'end')
2356
.text(tempDisabled.map(function(d) { return d.key }).join(', ') + ' values cannot be calculated for this time period.');
2357
}
2358
2359
//------------------------------------------------------------
2360
// Main Chart Component(s)
2361
2362
//------------------------------------------------------------
2363
//Set up interactive layer
2364
if (useInteractiveGuideline) {
2365
interactiveLayer
2366
.width(availableWidth)
2367
.height(availableHeight)
2368
.margin({left:margin.left,top:margin.top})
2369
.svgContainer(container)
2370
.xScale(x);
2371
wrap.select(".nv-interactive").call(interactiveLayer);
2372
}
2373
2374
gEnter.select('.nv-background')
2375
.append('rect');
2376
2377
g.select('.nv-background rect')
2378
.attr('width', availableWidth)
2379
.attr('height', availableHeight);
2380
2381
lines
2382
//.x(function(d) { return d.x })
2383
.y(function(d) { return d.display.y })
2384
.width(availableWidth)
2385
.height(availableHeight)
2386
.color(data.map(function(d,i) {
2387
return d.color || color(d, i);
2388
}).filter(function(d,i) { return !data[i].disabled && !data[i].tempDisabled; }));
2389
2390
2391
2392
var linesWrap = g.select('.nv-linesWrap')
2393
.datum(data.filter(function(d) { return !d.disabled && !d.tempDisabled }));
2394
2395
//d3.transition(linesWrap).call(lines);
2396
linesWrap.call(lines);
2397
2398
/*Handle average lines [AN-612] ----------------------------*/
2399
2400
//Store a series index number in the data array.
2401
data.forEach(function(d,i) {
2402
d.seriesIndex = i;
2403
});
2404
2405
var avgLineData = data.filter(function(d) {
2406
return !d.disabled && !!average(d);
2407
});
2408
2409
var avgLines = g.select(".nv-avgLinesWrap").selectAll("line")
2410
.data(avgLineData, function(d) { return d.key; });
2411
2412
var getAvgLineY = function(d) {
2413
//If average lines go off the svg element, clamp them to the svg bounds.
2414
var yVal = y(average(d));
2415
if (yVal < 0) return 0;
2416
if (yVal > availableHeight) return availableHeight;
2417
return yVal;
2418
};
2419
2420
avgLines.enter()
2421
.append('line')
2422
.style('stroke-width',2)
2423
.style('stroke-dasharray','10,10')
2424
.style('stroke',function (d,i) {
2425
return lines.color()(d,d.seriesIndex);
2426
})
2427
.attr('x1',0)
2428
.attr('x2',availableWidth)
2429
.attr('y1', getAvgLineY)
2430
.attr('y2', getAvgLineY);
2431
2432
avgLines
2433
.style('stroke-opacity',function(d){
2434
//If average lines go offscreen, make them transparent
2435
var yVal = y(average(d));
2436
if (yVal < 0 || yVal > availableHeight) return 0;
2437
return 1;
2438
})
2439
.attr('x1',0)
2440
.attr('x2',availableWidth)
2441
.attr('y1', getAvgLineY)
2442
.attr('y2', getAvgLineY);
2443
2444
avgLines.exit().remove();
2445
2446
//Create index line -----------------------------------------
2447
2448
var indexLine = linesWrap.selectAll('.nv-indexLine')
2449
.data([index]);
2450
indexLine.enter().append('rect').attr('class', 'nv-indexLine')
2451
.attr('width', 3)
2452
.attr('x', -2)
2453
.attr('fill', 'red')
2454
.attr('fill-opacity', .5)
2455
.style("pointer-events","all")
2456
.call(indexDrag)
2457
2458
indexLine
2459
.attr('transform', function(d) { return 'translate(' + dx(d.i) + ',0)' })
2460
.attr('height', availableHeight)
2461
2462
//------------------------------------------------------------
2463
2464
2465
//------------------------------------------------------------
2466
// Setup Axes
2467
2468
if (showXAxis) {
2469
xAxis
2470
.scale(x)
2471
//Suggest how many ticks based on the chart width and D3 should listen (70 is the optimal number for MM/DD/YY dates)
2472
.ticks( Math.min(data[0].values.length,availableWidth/70) )
2473
.tickSize(-availableHeight, 0);
2474
2475
g.select('.nv-x.nv-axis')
2476
.attr('transform', 'translate(0,' + y.range()[0] + ')');
2477
d3.transition(g.select('.nv-x.nv-axis'))
2478
.call(xAxis);
2479
}
2480
2481
2482
if (showYAxis) {
2483
yAxis
2484
.scale(y)
2485
.ticks( availableHeight / 36 )
2486
.tickSize( -availableWidth, 0);
2487
2488
d3.transition(g.select('.nv-y.nv-axis'))
2489
.call(yAxis);
2490
}
2491
//------------------------------------------------------------
2492
2493
2494
//============================================================
2495
// Event Handling/Dispatching (in chart's scope)
2496
//------------------------------------------------------------
2497
2498
2499
function updateZero() {
2500
indexLine
2501
.data([index]);
2502
2503
//When dragging the index line, turn off line transitions.
2504
// Then turn them back on when done dragging.
2505
var oldDuration = chart.transitionDuration();
2506
chart.transitionDuration(0);
2507
chart.update();
2508
chart.transitionDuration(oldDuration);
2509
}
2510
2511
g.select('.nv-background rect')
2512
.on('click', function() {
2513
index.x = d3.mouse(this)[0];
2514
index.i = Math.round(dx.invert(index.x));
2515
2516
// update state and send stateChange with new index
2517
state.index = index.i;
2518
dispatch.stateChange(state);
2519
2520
updateZero();
2521
});
2522
2523
lines.dispatch.on('elementClick', function(e) {
2524
index.i = e.pointIndex;
2525
index.x = dx(index.i);
2526
2527
// update state and send stateChange with new index
2528
state.index = index.i;
2529
dispatch.stateChange(state);
2530
2531
updateZero();
2532
});
2533
2534
controls.dispatch.on('legendClick', function(d,i) {
2535
d.disabled = !d.disabled;
2536
rescaleY = !d.disabled;
2537
2538
state.rescaleY = rescaleY;
2539
dispatch.stateChange(state);
2540
chart.update();
2541
});
2542
2543
2544
legend.dispatch.on('stateChange', function(newState) {
2545
state.disabled = newState.disabled;
2546
dispatch.stateChange(state);
2547
chart.update();
2548
});
2549
2550
interactiveLayer.dispatch.on('elementMousemove', function(e) {
2551
lines.clearHighlights();
2552
var singlePoint, pointIndex, pointXLocation, allData = [];
2553
data
2554
.filter(function(series, i) {
2555
series.seriesIndex = i;
2556
return !series.disabled;
2557
})
2558
.forEach(function(series,i) {
2559
pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
2560
lines.highlightPoint(i, pointIndex, true);
2561
var point = series.values[pointIndex];
2562
if (typeof point === 'undefined') return;
2563
if (typeof singlePoint === 'undefined') singlePoint = point;
2564
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
2565
allData.push({
2566
key: series.key,
2567
value: chart.y()(point, pointIndex),
2568
color: color(series,series.seriesIndex)
2569
});
2570
});
2571
2572
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
2573
interactiveLayer.tooltip
2574
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
2575
.chartContainer(that.parentNode)
2576
.enabled(tooltips)
2577
.valueFormatter(function(d,i) {
2578
return yAxis.tickFormat()(d);
2579
})
2580
.data(
2581
{
2582
value: xValue,
2583
series: allData
2584
}
2585
)();
2586
2587
interactiveLayer.renderGuideLine(pointXLocation);
2588
2589
});
2590
2591
interactiveLayer.dispatch.on("elementMouseout",function(e) {
2592
dispatch.tooltipHide();
2593
lines.clearHighlights();
2594
});
2595
2596
dispatch.on('tooltipShow', function(e) {
2597
if (tooltips) showTooltip(e, that.parentNode);
2598
});
2599
2600
2601
// Update chart from a state object passed to event handler
2602
dispatch.on('changeState', function(e) {
2603
2604
if (typeof e.disabled !== 'undefined') {
2605
data.forEach(function(series,i) {
2606
series.disabled = e.disabled[i];
2607
});
2608
2609
state.disabled = e.disabled;
2610
}
2611
2612
2613
if (typeof e.index !== 'undefined') {
2614
index.i = e.index;
2615
index.x = dx(index.i);
2616
2617
state.index = e.index;
2618
2619
indexLine
2620
.data([index]);
2621
}
2622
2623
2624
if (typeof e.rescaleY !== 'undefined') {
2625
rescaleY = e.rescaleY;
2626
}
2627
2628
chart.update();
2629
});
2630
2631
//============================================================
2632
2633
});
2634
2635
return chart;
2636
}
2637
2638
2639
//============================================================
2640
// Event Handling/Dispatching (out of chart's scope)
2641
//------------------------------------------------------------
2642
2643
lines.dispatch.on('elementMouseover.tooltip', function(e) {
2644
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
2645
dispatch.tooltipShow(e);
2646
});
2647
2648
lines.dispatch.on('elementMouseout.tooltip', function(e) {
2649
dispatch.tooltipHide(e);
2650
});
2651
2652
dispatch.on('tooltipHide', function() {
2653
if (tooltips) nv.tooltip.cleanup();
2654
});
2655
2656
//============================================================
2657
2658
2659
//============================================================
2660
// Expose Public Variables
2661
//------------------------------------------------------------
2662
2663
// expose chart's sub-components
2664
chart.dispatch = dispatch;
2665
chart.lines = lines;
2666
chart.legend = legend;
2667
chart.xAxis = xAxis;
2668
chart.yAxis = yAxis;
2669
chart.interactiveLayer = interactiveLayer;
2670
2671
d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'xScale','yScale', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi','useVoronoi', 'id');
2672
2673
chart.options = nv.utils.optionsFunc.bind(chart);
2674
2675
chart.margin = function(_) {
2676
if (!arguments.length) return margin;
2677
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
2678
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
2679
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
2680
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
2681
return chart;
2682
};
2683
2684
chart.width = function(_) {
2685
if (!arguments.length) return width;
2686
width = _;
2687
return chart;
2688
};
2689
2690
chart.height = function(_) {
2691
if (!arguments.length) return height;
2692
height = _;
2693
return chart;
2694
};
2695
2696
chart.color = function(_) {
2697
if (!arguments.length) return color;
2698
color = nv.utils.getColor(_);
2699
legend.color(color);
2700
return chart;
2701
};
2702
2703
chart.rescaleY = function(_) {
2704
if (!arguments.length) return rescaleY;
2705
rescaleY = _
2706
return rescaleY;
2707
};
2708
2709
chart.showControls = function(_) {
2710
if (!arguments.length) return showControls;
2711
showControls = _;
2712
return chart;
2713
};
2714
2715
chart.useInteractiveGuideline = function(_) {
2716
if(!arguments.length) return useInteractiveGuideline;
2717
useInteractiveGuideline = _;
2718
if (_ === true) {
2719
chart.interactive(false);
2720
chart.useVoronoi(false);
2721
}
2722
return chart;
2723
};
2724
2725
chart.showLegend = function(_) {
2726
if (!arguments.length) return showLegend;
2727
showLegend = _;
2728
return chart;
2729
};
2730
2731
chart.showXAxis = function(_) {
2732
if (!arguments.length) return showXAxis;
2733
showXAxis = _;
2734
return chart;
2735
};
2736
2737
chart.showYAxis = function(_) {
2738
if (!arguments.length) return showYAxis;
2739
showYAxis = _;
2740
return chart;
2741
};
2742
2743
chart.rightAlignYAxis = function(_) {
2744
if(!arguments.length) return rightAlignYAxis;
2745
rightAlignYAxis = _;
2746
yAxis.orient( (_) ? 'right' : 'left');
2747
return chart;
2748
};
2749
2750
chart.tooltips = function(_) {
2751
if (!arguments.length) return tooltips;
2752
tooltips = _;
2753
return chart;
2754
};
2755
2756
chart.tooltipContent = function(_) {
2757
if (!arguments.length) return tooltip;
2758
tooltip = _;
2759
return chart;
2760
};
2761
2762
chart.state = function(_) {
2763
if (!arguments.length) return state;
2764
state = _;
2765
return chart;
2766
};
2767
2768
chart.defaultState = function(_) {
2769
if (!arguments.length) return defaultState;
2770
defaultState = _;
2771
return chart;
2772
};
2773
2774
chart.noData = function(_) {
2775
if (!arguments.length) return noData;
2776
noData = _;
2777
return chart;
2778
};
2779
2780
chart.average = function(_) {
2781
if(!arguments.length) return average;
2782
average = _;
2783
return chart;
2784
};
2785
2786
chart.transitionDuration = function(_) {
2787
if (!arguments.length) return transitionDuration;
2788
transitionDuration = _;
2789
return chart;
2790
};
2791
2792
//============================================================
2793
2794
2795
//============================================================
2796
// Functions
2797
//------------------------------------------------------------
2798
2799
/* Normalize the data according to an index point. */
2800
function indexify(idx, data) {
2801
return data.map(function(line, i) {
2802
if (!line.values) {
2803
return line;
2804
}
2805
var v = lines.y()(line.values[idx], idx);
2806
2807
//TODO: implement check below, and disable series if series loses 100% or more cause divide by 0 issue
2808
if (v < -.95) {
2809
//if a series loses more than 100%, calculations fail.. anything close can cause major distortion (but is mathematically correct till it hits 100)
2810
line.tempDisabled = true;
2811
return line;
2812
}
2813
2814
line.tempDisabled = false;
2815
2816
line.values = line.values.map(function(point, pointIndex) {
2817
point.display = {'y': (lines.y()(point, pointIndex) - v) / (1 + v) };
2818
return point;
2819
})
2820
2821
return line;
2822
})
2823
}
2824
2825
//============================================================
2826
2827
2828
return chart;
2829
}
2830
//TODO: consider deprecating by adding necessary features to multiBar model
2831
nv.models.discreteBar = function() {
2832
"use strict";
2833
//============================================================
2834
// Public Variables with Default Settings
2835
//------------------------------------------------------------
2836
2837
var margin = {top: 0, right: 0, bottom: 0, left: 0}
2838
, width = 960
2839
, height = 500
2840
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
2841
, x = d3.scale.ordinal()
2842
, y = d3.scale.linear()
2843
, getX = function(d) { return d.x }
2844
, getY = function(d) { return d.y }
2845
, forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
2846
, color = nv.utils.defaultColor()
2847
, showValues = false
2848
, valueFormat = d3.format(',.2f')
2849
, xDomain
2850
, yDomain
2851
, xRange
2852
, yRange
2853
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
2854
, rectClass = 'discreteBar'
2855
;
2856
2857
//============================================================
2858
2859
2860
//============================================================
2861
// Private Variables
2862
//------------------------------------------------------------
2863
2864
var x0, y0;
2865
2866
//============================================================
2867
2868
2869
function chart(selection) {
2870
selection.each(function(data) {
2871
var availableWidth = width - margin.left - margin.right,
2872
availableHeight = height - margin.top - margin.bottom,
2873
container = d3.select(this);
2874
2875
2876
//add series index to each data point for reference
2877
data = data.map(function(series, i) {
2878
series.values = series.values.map(function(point) {
2879
point.series = i;
2880
return point;
2881
});
2882
return series;
2883
});
2884
2885
2886
//------------------------------------------------------------
2887
// Setup Scales
2888
2889
// remap and flatten the data for use in calculating the scales' domains
2890
var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
2891
data.map(function(d) {
2892
return d.values.map(function(d,i) {
2893
return { x: getX(d,i), y: getY(d,i), y0: d.y0 }
2894
})
2895
});
2896
2897
x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
2898
.rangeBands(xRange || [0, availableWidth], .1);
2899
2900
y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y }).concat(forceY)));
2901
2902
2903
// If showValues, pad the Y axis range to account for label height
2904
if (showValues) y.range(yRange || [availableHeight - (y.domain()[0] < 0 ? 12 : 0), y.domain()[1] > 0 ? 12 : 0]);
2905
else y.range(yRange || [availableHeight, 0]);
2906
2907
//store old scales if they exist
2908
x0 = x0 || x;
2909
y0 = y0 || y.copy().range([y(0),y(0)]);
2910
2911
//------------------------------------------------------------
2912
2913
2914
//------------------------------------------------------------
2915
// Setup containers and skeleton of chart
2916
2917
var wrap = container.selectAll('g.nv-wrap.nv-discretebar').data([data]);
2918
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discretebar');
2919
var gEnter = wrapEnter.append('g');
2920
var g = wrap.select('g');
2921
2922
gEnter.append('g').attr('class', 'nv-groups');
2923
2924
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
2925
2926
//------------------------------------------------------------
2927
2928
2929
2930
//TODO: by definition, the discrete bar should not have multiple groups, will modify/remove later
2931
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
2932
.data(function(d) { return d }, function(d) { return d.key });
2933
groups.enter().append('g')
2934
.style('stroke-opacity', 1e-6)
2935
.style('fill-opacity', 1e-6);
2936
groups.exit()
2937
.transition()
2938
.style('stroke-opacity', 1e-6)
2939
.style('fill-opacity', 1e-6)
2940
.remove();
2941
groups
2942
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
2943
.classed('hover', function(d) { return d.hover });
2944
groups
2945
.transition()
2946
.style('stroke-opacity', 1)
2947
.style('fill-opacity', .75);
2948
2949
2950
var bars = groups.selectAll('g.nv-bar')
2951
.data(function(d) { return d.values });
2952
2953
bars.exit().remove();
2954
2955
2956
var barsEnter = bars.enter().append('g')
2957
.attr('transform', function(d,i,j) {
2958
return 'translate(' + (x(getX(d,i)) + x.rangeBand() * .05 ) + ', ' + y(0) + ')'
2959
})
2960
.on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
2961
d3.select(this).classed('hover', true);
2962
dispatch.elementMouseover({
2963
value: getY(d,i),
2964
point: d,
2965
series: data[d.series],
2966
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
2967
pointIndex: i,
2968
seriesIndex: d.series,
2969
e: d3.event
2970
});
2971
})
2972
.on('mouseout', function(d,i) {
2973
d3.select(this).classed('hover', false);
2974
dispatch.elementMouseout({
2975
value: getY(d,i),
2976
point: d,
2977
series: data[d.series],
2978
pointIndex: i,
2979
seriesIndex: d.series,
2980
e: d3.event
2981
});
2982
})
2983
.on('click', function(d,i) {
2984
dispatch.elementClick({
2985
value: getY(d,i),
2986
point: d,
2987
series: data[d.series],
2988
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
2989
pointIndex: i,
2990
seriesIndex: d.series,
2991
e: d3.event
2992
});
2993
d3.event.stopPropagation();
2994
})
2995
.on('dblclick', function(d,i) {
2996
dispatch.elementDblClick({
2997
value: getY(d,i),
2998
point: d,
2999
series: data[d.series],
3000
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
3001
pointIndex: i,
3002
seriesIndex: d.series,
3003
e: d3.event
3004
});
3005
d3.event.stopPropagation();
3006
});
3007
3008
barsEnter.append('rect')
3009
.attr('height', 0)
3010
.attr('width', x.rangeBand() * .9 / data.length )
3011
3012
if (showValues) {
3013
barsEnter.append('text')
3014
.attr('text-anchor', 'middle')
3015
;
3016
3017
bars.select('text')
3018
.text(function(d,i) { return valueFormat(getY(d,i)) })
3019
.transition()
3020
.attr('x', x.rangeBand() * .9 / 2)
3021
.attr('y', function(d,i) { return getY(d,i) < 0 ? y(getY(d,i)) - y(0) + 12 : -4 })
3022
3023
;
3024
} else {
3025
bars.selectAll('text').remove();
3026
}
3027
3028
bars
3029
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive' })
3030
.style('fill', function(d,i) { return d.color || color(d,i) })
3031
.style('stroke', function(d,i) { return d.color || color(d,i) })
3032
.select('rect')
3033
.attr('class', rectClass)
3034
.transition()
3035
.attr('width', x.rangeBand() * .9 / data.length);
3036
bars.transition()
3037
//.delay(function(d,i) { return i * 1200 / data[0].values.length })
3038
.attr('transform', function(d,i) {
3039
var left = x(getX(d,i)) + x.rangeBand() * .05,
3040
top = getY(d,i) < 0 ?
3041
y(0) :
3042
y(0) - y(getY(d,i)) < 1 ?
3043
y(0) - 1 : //make 1 px positive bars show up above y=0
3044
y(getY(d,i));
3045
3046
return 'translate(' + left + ', ' + top + ')'
3047
})
3048
.select('rect')
3049
.attr('height', function(d,i) {
3050
return Math.max(Math.abs(y(getY(d,i)) - y((yDomain && yDomain[0]) || 0)) || 1)
3051
});
3052
3053
3054
//store old scales for use in transitions on update
3055
x0 = x.copy();
3056
y0 = y.copy();
3057
3058
});
3059
3060
return chart;
3061
}
3062
3063
3064
//============================================================
3065
// Expose Public Variables
3066
//------------------------------------------------------------
3067
3068
chart.dispatch = dispatch;
3069
3070
chart.options = nv.utils.optionsFunc.bind(chart);
3071
3072
chart.x = function(_) {
3073
if (!arguments.length) return getX;
3074
getX = _;
3075
return chart;
3076
};
3077
3078
chart.y = function(_) {
3079
if (!arguments.length) return getY;
3080
getY = _;
3081
return chart;
3082
};
3083
3084
chart.margin = function(_) {
3085
if (!arguments.length) return margin;
3086
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3087
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3088
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3089
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3090
return chart;
3091
};
3092
3093
chart.width = function(_) {
3094
if (!arguments.length) return width;
3095
width = _;
3096
return chart;
3097
};
3098
3099
chart.height = function(_) {
3100
if (!arguments.length) return height;
3101
height = _;
3102
return chart;
3103
};
3104
3105
chart.xScale = function(_) {
3106
if (!arguments.length) return x;
3107
x = _;
3108
return chart;
3109
};
3110
3111
chart.yScale = function(_) {
3112
if (!arguments.length) return y;
3113
y = _;
3114
return chart;
3115
};
3116
3117
chart.xDomain = function(_) {
3118
if (!arguments.length) return xDomain;
3119
xDomain = _;
3120
return chart;
3121
};
3122
3123
chart.yDomain = function(_) {
3124
if (!arguments.length) return yDomain;
3125
yDomain = _;
3126
return chart;
3127
};
3128
3129
chart.xRange = function(_) {
3130
if (!arguments.length) return xRange;
3131
xRange = _;
3132
return chart;
3133
};
3134
3135
chart.yRange = function(_) {
3136
if (!arguments.length) return yRange;
3137
yRange = _;
3138
return chart;
3139
};
3140
3141
chart.forceY = function(_) {
3142
if (!arguments.length) return forceY;
3143
forceY = _;
3144
return chart;
3145
};
3146
3147
chart.color = function(_) {
3148
if (!arguments.length) return color;
3149
color = nv.utils.getColor(_);
3150
return chart;
3151
};
3152
3153
chart.id = function(_) {
3154
if (!arguments.length) return id;
3155
id = _;
3156
return chart;
3157
};
3158
3159
chart.showValues = function(_) {
3160
if (!arguments.length) return showValues;
3161
showValues = _;
3162
return chart;
3163
};
3164
3165
chart.valueFormat= function(_) {
3166
if (!arguments.length) return valueFormat;
3167
valueFormat = _;
3168
return chart;
3169
};
3170
3171
chart.rectClass= function(_) {
3172
if (!arguments.length) return rectClass;
3173
rectClass = _;
3174
return chart;
3175
};
3176
//============================================================
3177
3178
3179
return chart;
3180
}
3181
3182
nv.models.discreteBarChart = function() {
3183
"use strict";
3184
//============================================================
3185
// Public Variables with Default Settings
3186
//------------------------------------------------------------
3187
3188
var discretebar = nv.models.discreteBar()
3189
, xAxis = nv.models.axis()
3190
, yAxis = nv.models.axis()
3191
;
3192
3193
var margin = {top: 15, right: 10, bottom: 50, left: 60}
3194
, width = null
3195
, height = null
3196
, color = nv.utils.getColor()
3197
, showXAxis = true
3198
, showYAxis = true
3199
, rightAlignYAxis = false
3200
, staggerLabels = false
3201
, tooltips = true
3202
, tooltip = function(key, x, y, e, graph) {
3203
return '<h3>' + x + '</h3>' +
3204
'<p>' + y + '</p>'
3205
}
3206
, x
3207
, y
3208
, noData = "No Data Available."
3209
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'beforeUpdate')
3210
, transitionDuration = 250
3211
;
3212
3213
xAxis
3214
.orient('bottom')
3215
.highlightZero(false)
3216
.showMaxMin(false)
3217
.tickFormat(function(d) { return d })
3218
;
3219
yAxis
3220
.orient((rightAlignYAxis) ? 'right' : 'left')
3221
.tickFormat(d3.format(',.1f'))
3222
;
3223
3224
//============================================================
3225
3226
3227
//============================================================
3228
// Private Variables
3229
//------------------------------------------------------------
3230
3231
var showTooltip = function(e, offsetElement) {
3232
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
3233
top = e.pos[1] + ( offsetElement.offsetTop || 0),
3234
x = xAxis.tickFormat()(discretebar.x()(e.point, e.pointIndex)),
3235
y = yAxis.tickFormat()(discretebar.y()(e.point, e.pointIndex)),
3236
content = tooltip(e.series.key, x, y, e, chart);
3237
3238
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
3239
};
3240
3241
//============================================================
3242
3243
3244
function chart(selection) {
3245
selection.each(function(data) {
3246
var container = d3.select(this),
3247
that = this;
3248
3249
var availableWidth = (width || parseInt(container.style('width')) || 960)
3250
- margin.left - margin.right,
3251
availableHeight = (height || parseInt(container.style('height')) || 400)
3252
- margin.top - margin.bottom;
3253
3254
3255
chart.update = function() {
3256
dispatch.beforeUpdate();
3257
container.transition().duration(transitionDuration).call(chart);
3258
};
3259
chart.container = this;
3260
3261
3262
//------------------------------------------------------------
3263
// Display No Data message if there's nothing to show.
3264
3265
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
3266
var noDataText = container.selectAll('.nv-noData').data([noData]);
3267
3268
noDataText.enter().append('text')
3269
.attr('class', 'nvd3 nv-noData')
3270
.attr('dy', '-.7em')
3271
.style('text-anchor', 'middle');
3272
3273
noDataText
3274
.attr('x', margin.left + availableWidth / 2)
3275
.attr('y', margin.top + availableHeight / 2)
3276
.text(function(d) { return d });
3277
3278
return chart;
3279
} else {
3280
container.selectAll('.nv-noData').remove();
3281
}
3282
3283
//------------------------------------------------------------
3284
3285
3286
//------------------------------------------------------------
3287
// Setup Scales
3288
3289
x = discretebar.xScale();
3290
y = discretebar.yScale().clamp(true);
3291
3292
//------------------------------------------------------------
3293
3294
3295
//------------------------------------------------------------
3296
// Setup containers and skeleton of chart
3297
3298
var wrap = container.selectAll('g.nv-wrap.nv-discreteBarWithAxes').data([data]);
3299
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-discreteBarWithAxes').append('g');
3300
var defsEnter = gEnter.append('defs');
3301
var g = wrap.select('g');
3302
3303
gEnter.append('g').attr('class', 'nv-x nv-axis');
3304
gEnter.append('g').attr('class', 'nv-y nv-axis');
3305
gEnter.append('g').attr('class', 'nv-barsWrap');
3306
3307
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3308
3309
if (rightAlignYAxis) {
3310
g.select(".nv-y.nv-axis")
3311
.attr("transform", "translate(" + availableWidth + ",0)");
3312
}
3313
3314
//------------------------------------------------------------
3315
3316
3317
//------------------------------------------------------------
3318
// Main Chart Component(s)
3319
3320
discretebar
3321
.width(availableWidth)
3322
.height(availableHeight);
3323
3324
3325
var barsWrap = g.select('.nv-barsWrap')
3326
.datum(data.filter(function(d) { return !d.disabled }))
3327
3328
barsWrap.transition().call(discretebar);
3329
3330
//------------------------------------------------------------
3331
3332
3333
3334
defsEnter.append('clipPath')
3335
.attr('id', 'nv-x-label-clip-' + discretebar.id())
3336
.append('rect');
3337
3338
g.select('#nv-x-label-clip-' + discretebar.id() + ' rect')
3339
.attr('width', x.rangeBand() * (staggerLabels ? 2 : 1))
3340
.attr('height', 16)
3341
.attr('x', -x.rangeBand() / (staggerLabels ? 1 : 2 ));
3342
3343
3344
//------------------------------------------------------------
3345
// Setup Axes
3346
3347
if (showXAxis) {
3348
xAxis
3349
.scale(x)
3350
.ticks( availableWidth / 100 )
3351
.tickSize(-availableHeight, 0);
3352
3353
g.select('.nv-x.nv-axis')
3354
.attr('transform', 'translate(0,' + (y.range()[0] + ((discretebar.showValues() && y.domain()[0] < 0) ? 16 : 0)) + ')');
3355
//d3.transition(g.select('.nv-x.nv-axis'))
3356
g.select('.nv-x.nv-axis').transition()
3357
.call(xAxis);
3358
3359
3360
var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
3361
3362
if (staggerLabels) {
3363
xTicks
3364
.selectAll('text')
3365
.attr('transform', function(d,i,j) { return 'translate(0,' + (j % 2 == 0 ? '5' : '17') + ')' })
3366
}
3367
}
3368
3369
if (showYAxis) {
3370
yAxis
3371
.scale(y)
3372
.ticks( availableHeight / 36 )
3373
.tickSize( -availableWidth, 0);
3374
3375
g.select('.nv-y.nv-axis').transition()
3376
.call(yAxis);
3377
}
3378
3379
//------------------------------------------------------------
3380
3381
3382
//============================================================
3383
// Event Handling/Dispatching (in chart's scope)
3384
//------------------------------------------------------------
3385
3386
dispatch.on('tooltipShow', function(e) {
3387
if (tooltips) showTooltip(e, that.parentNode);
3388
});
3389
3390
//============================================================
3391
3392
3393
});
3394
3395
return chart;
3396
}
3397
3398
//============================================================
3399
// Event Handling/Dispatching (out of chart's scope)
3400
//------------------------------------------------------------
3401
3402
discretebar.dispatch.on('elementMouseover.tooltip', function(e) {
3403
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
3404
dispatch.tooltipShow(e);
3405
});
3406
3407
discretebar.dispatch.on('elementMouseout.tooltip', function(e) {
3408
dispatch.tooltipHide(e);
3409
});
3410
3411
dispatch.on('tooltipHide', function() {
3412
if (tooltips) nv.tooltip.cleanup();
3413
});
3414
3415
//============================================================
3416
3417
3418
//============================================================
3419
// Expose Public Variables
3420
//------------------------------------------------------------
3421
3422
// expose chart's sub-components
3423
chart.dispatch = dispatch;
3424
chart.discretebar = discretebar;
3425
chart.xAxis = xAxis;
3426
chart.yAxis = yAxis;
3427
3428
d3.rebind(chart, discretebar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'id', 'showValues', 'valueFormat');
3429
3430
chart.options = nv.utils.optionsFunc.bind(chart);
3431
3432
chart.margin = function(_) {
3433
if (!arguments.length) return margin;
3434
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3435
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3436
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3437
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3438
return chart;
3439
};
3440
3441
chart.width = function(_) {
3442
if (!arguments.length) return width;
3443
width = _;
3444
return chart;
3445
};
3446
3447
chart.height = function(_) {
3448
if (!arguments.length) return height;
3449
height = _;
3450
return chart;
3451
};
3452
3453
chart.color = function(_) {
3454
if (!arguments.length) return color;
3455
color = nv.utils.getColor(_);
3456
discretebar.color(color);
3457
return chart;
3458
};
3459
3460
chart.showXAxis = function(_) {
3461
if (!arguments.length) return showXAxis;
3462
showXAxis = _;
3463
return chart;
3464
};
3465
3466
chart.showYAxis = function(_) {
3467
if (!arguments.length) return showYAxis;
3468
showYAxis = _;
3469
return chart;
3470
};
3471
3472
chart.rightAlignYAxis = function(_) {
3473
if(!arguments.length) return rightAlignYAxis;
3474
rightAlignYAxis = _;
3475
yAxis.orient( (_) ? 'right' : 'left');
3476
return chart;
3477
};
3478
3479
chart.staggerLabels = function(_) {
3480
if (!arguments.length) return staggerLabels;
3481
staggerLabels = _;
3482
return chart;
3483
};
3484
3485
chart.tooltips = function(_) {
3486
if (!arguments.length) return tooltips;
3487
tooltips = _;
3488
return chart;
3489
};
3490
3491
chart.tooltipContent = function(_) {
3492
if (!arguments.length) return tooltip;
3493
tooltip = _;
3494
return chart;
3495
};
3496
3497
chart.noData = function(_) {
3498
if (!arguments.length) return noData;
3499
noData = _;
3500
return chart;
3501
};
3502
3503
chart.transitionDuration = function(_) {
3504
if (!arguments.length) return transitionDuration;
3505
transitionDuration = _;
3506
return chart;
3507
};
3508
3509
//============================================================
3510
3511
3512
return chart;
3513
}
3514
3515
nv.models.distribution = function() {
3516
"use strict";
3517
//============================================================
3518
// Public Variables with Default Settings
3519
//------------------------------------------------------------
3520
3521
var margin = {top: 0, right: 0, bottom: 0, left: 0}
3522
, width = 400 //technically width or height depending on x or y....
3523
, size = 8
3524
, axis = 'x' // 'x' or 'y'... horizontal or vertical
3525
, getData = function(d) { return d[axis] } // defaults d.x or d.y
3526
, color = nv.utils.defaultColor()
3527
, scale = d3.scale.linear()
3528
, domain
3529
;
3530
3531
//============================================================
3532
3533
3534
//============================================================
3535
// Private Variables
3536
//------------------------------------------------------------
3537
3538
var scale0;
3539
3540
//============================================================
3541
3542
3543
function chart(selection) {
3544
selection.each(function(data) {
3545
var availableLength = width - (axis === 'x' ? margin.left + margin.right : margin.top + margin.bottom),
3546
naxis = axis == 'x' ? 'y' : 'x',
3547
container = d3.select(this);
3548
3549
3550
//------------------------------------------------------------
3551
// Setup Scales
3552
3553
scale0 = scale0 || scale;
3554
3555
//------------------------------------------------------------
3556
3557
3558
//------------------------------------------------------------
3559
// Setup containers and skeleton of chart
3560
3561
var wrap = container.selectAll('g.nv-distribution').data([data]);
3562
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-distribution');
3563
var gEnter = wrapEnter.append('g');
3564
var g = wrap.select('g');
3565
3566
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
3567
3568
//------------------------------------------------------------
3569
3570
3571
var distWrap = g.selectAll('g.nv-dist')
3572
.data(function(d) { return d }, function(d) { return d.key });
3573
3574
distWrap.enter().append('g');
3575
distWrap
3576
.attr('class', function(d,i) { return 'nv-dist nv-series-' + i })
3577
.style('stroke', function(d,i) { return color(d, i) });
3578
3579
var dist = distWrap.selectAll('line.nv-dist' + axis)
3580
.data(function(d) { return d.values })
3581
dist.enter().append('line')
3582
.attr(axis + '1', function(d,i) { return scale0(getData(d,i)) })
3583
.attr(axis + '2', function(d,i) { return scale0(getData(d,i)) })
3584
distWrap.exit().selectAll('line.nv-dist' + axis)
3585
.transition()
3586
.attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3587
.attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3588
.style('stroke-opacity', 0)
3589
.remove();
3590
dist
3591
.attr('class', function(d,i) { return 'nv-dist' + axis + ' nv-dist' + axis + '-' + i })
3592
.attr(naxis + '1', 0)
3593
.attr(naxis + '2', size);
3594
dist
3595
.transition()
3596
.attr(axis + '1', function(d,i) { return scale(getData(d,i)) })
3597
.attr(axis + '2', function(d,i) { return scale(getData(d,i)) })
3598
3599
3600
scale0 = scale.copy();
3601
3602
});
3603
3604
return chart;
3605
}
3606
3607
3608
//============================================================
3609
// Expose Public Variables
3610
//------------------------------------------------------------
3611
chart.options = nv.utils.optionsFunc.bind(chart);
3612
3613
chart.margin = function(_) {
3614
if (!arguments.length) return margin;
3615
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3616
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3617
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3618
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3619
return chart;
3620
};
3621
3622
chart.width = function(_) {
3623
if (!arguments.length) return width;
3624
width = _;
3625
return chart;
3626
};
3627
3628
chart.axis = function(_) {
3629
if (!arguments.length) return axis;
3630
axis = _;
3631
return chart;
3632
};
3633
3634
chart.size = function(_) {
3635
if (!arguments.length) return size;
3636
size = _;
3637
return chart;
3638
};
3639
3640
chart.getData = function(_) {
3641
if (!arguments.length) return getData;
3642
getData = d3.functor(_);
3643
return chart;
3644
};
3645
3646
chart.scale = function(_) {
3647
if (!arguments.length) return scale;
3648
scale = _;
3649
return chart;
3650
};
3651
3652
chart.color = function(_) {
3653
if (!arguments.length) return color;
3654
color = nv.utils.getColor(_);
3655
return chart;
3656
};
3657
//============================================================
3658
3659
3660
return chart;
3661
}
3662
//TODO: consider deprecating and using multibar with single series for this
3663
nv.models.historicalBar = function() {
3664
"use strict";
3665
//============================================================
3666
// Public Variables with Default Settings
3667
//------------------------------------------------------------
3668
3669
var margin = {top: 0, right: 0, bottom: 0, left: 0}
3670
, width = 960
3671
, height = 500
3672
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
3673
, x = d3.scale.linear()
3674
, y = d3.scale.linear()
3675
, getX = function(d) { return d.x }
3676
, getY = function(d) { return d.y }
3677
, forceX = []
3678
, forceY = [0]
3679
, padData = false
3680
, clipEdge = true
3681
, color = nv.utils.defaultColor()
3682
, xDomain
3683
, yDomain
3684
, xRange
3685
, yRange
3686
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
3687
, interactive = true
3688
;
3689
3690
//============================================================
3691
3692
3693
function chart(selection) {
3694
selection.each(function(data) {
3695
var availableWidth = width - margin.left - margin.right,
3696
availableHeight = height - margin.top - margin.bottom,
3697
container = d3.select(this);
3698
3699
3700
//------------------------------------------------------------
3701
// Setup Scales
3702
3703
x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ))
3704
3705
if (padData)
3706
x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
3707
else
3708
x.range(xRange || [0, availableWidth]);
3709
3710
y .domain(yDomain || d3.extent(data[0].values.map(getY).concat(forceY) ))
3711
.range(yRange || [availableHeight, 0]);
3712
3713
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
3714
3715
if (x.domain()[0] === x.domain()[1])
3716
x.domain()[0] ?
3717
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
3718
: x.domain([-1,1]);
3719
3720
if (y.domain()[0] === y.domain()[1])
3721
y.domain()[0] ?
3722
y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
3723
: y.domain([-1,1]);
3724
3725
//------------------------------------------------------------
3726
3727
3728
//------------------------------------------------------------
3729
// Setup containers and skeleton of chart
3730
3731
var wrap = container.selectAll('g.nv-wrap.nv-historicalBar-' + id).data([data[0].values]);
3732
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBar-' + id);
3733
var defsEnter = wrapEnter.append('defs');
3734
var gEnter = wrapEnter.append('g');
3735
var g = wrap.select('g');
3736
3737
gEnter.append('g').attr('class', 'nv-bars');
3738
3739
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
3740
3741
//------------------------------------------------------------
3742
3743
3744
container
3745
.on('click', function(d,i) {
3746
dispatch.chartClick({
3747
data: d,
3748
index: i,
3749
pos: d3.event,
3750
id: id
3751
});
3752
});
3753
3754
3755
defsEnter.append('clipPath')
3756
.attr('id', 'nv-chart-clip-path-' + id)
3757
.append('rect');
3758
3759
wrap.select('#nv-chart-clip-path-' + id + ' rect')
3760
.attr('width', availableWidth)
3761
.attr('height', availableHeight);
3762
3763
g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
3764
3765
3766
3767
var bars = wrap.select('.nv-bars').selectAll('.nv-bar')
3768
.data(function(d) { return d }, function(d,i) {return getX(d,i)});
3769
3770
bars.exit().remove();
3771
3772
3773
var barsEnter = bars.enter().append('rect')
3774
//.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3775
.attr('x', 0 )
3776
.attr('y', function(d,i) { return nv.utils.NaNtoZero(y(Math.max(0, getY(d,i)))) })
3777
.attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.abs(y(getY(d,i)) - y(0))) })
3778
.attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3779
.on('mouseover', function(d,i) {
3780
if (!interactive) return;
3781
d3.select(this).classed('hover', true);
3782
dispatch.elementMouseover({
3783
point: d,
3784
series: data[0],
3785
pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
3786
pointIndex: i,
3787
seriesIndex: 0,
3788
e: d3.event
3789
});
3790
3791
})
3792
.on('mouseout', function(d,i) {
3793
if (!interactive) return;
3794
d3.select(this).classed('hover', false);
3795
dispatch.elementMouseout({
3796
point: d,
3797
series: data[0],
3798
pointIndex: i,
3799
seriesIndex: 0,
3800
e: d3.event
3801
});
3802
})
3803
.on('click', function(d,i) {
3804
if (!interactive) return;
3805
dispatch.elementClick({
3806
//label: d[label],
3807
value: getY(d,i),
3808
data: d,
3809
index: i,
3810
pos: [x(getX(d,i)), y(getY(d,i))],
3811
e: d3.event,
3812
id: id
3813
});
3814
d3.event.stopPropagation();
3815
})
3816
.on('dblclick', function(d,i) {
3817
if (!interactive) return;
3818
dispatch.elementDblClick({
3819
//label: d[label],
3820
value: getY(d,i),
3821
data: d,
3822
index: i,
3823
pos: [x(getX(d,i)), y(getY(d,i))],
3824
e: d3.event,
3825
id: id
3826
});
3827
d3.event.stopPropagation();
3828
});
3829
3830
bars
3831
.attr('fill', function(d,i) { return color(d, i); })
3832
.attr('class', function(d,i,j) { return (getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive') + ' nv-bar-' + j + '-' + i })
3833
.transition()
3834
.attr('transform', function(d,i) { return 'translate(' + (x(getX(d,i)) - availableWidth / data[0].values.length * .45) + ',0)'; })
3835
//TODO: better width calculations that don't assume always uniform data spacing;w
3836
.attr('width', (availableWidth / data[0].values.length) * .9 );
3837
3838
3839
bars.transition()
3840
.attr('y', function(d,i) {
3841
var rval = getY(d,i) < 0 ?
3842
y(0) :
3843
y(0) - y(getY(d,i)) < 1 ?
3844
y(0) - 1 :
3845
y(getY(d,i));
3846
return nv.utils.NaNtoZero(rval);
3847
})
3848
.attr('height', function(d,i) { return nv.utils.NaNtoZero(Math.max(Math.abs(y(getY(d,i)) - y(0)),1)) });
3849
3850
});
3851
3852
return chart;
3853
}
3854
3855
//Create methods to allow outside functions to highlight a specific bar.
3856
chart.highlightPoint = function(pointIndex, isHoverOver) {
3857
d3.select(".nv-historicalBar-" + id)
3858
.select(".nv-bars .nv-bar-0-" + pointIndex)
3859
.classed("hover", isHoverOver)
3860
;
3861
};
3862
3863
chart.clearHighlights = function() {
3864
d3.select(".nv-historicalBar-" + id)
3865
.select(".nv-bars .nv-bar.hover")
3866
.classed("hover", false)
3867
;
3868
};
3869
//============================================================
3870
// Expose Public Variables
3871
//------------------------------------------------------------
3872
3873
chart.dispatch = dispatch;
3874
3875
chart.options = nv.utils.optionsFunc.bind(chart);
3876
3877
chart.x = function(_) {
3878
if (!arguments.length) return getX;
3879
getX = _;
3880
return chart;
3881
};
3882
3883
chart.y = function(_) {
3884
if (!arguments.length) return getY;
3885
getY = _;
3886
return chart;
3887
};
3888
3889
chart.margin = function(_) {
3890
if (!arguments.length) return margin;
3891
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
3892
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
3893
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
3894
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
3895
return chart;
3896
};
3897
3898
chart.width = function(_) {
3899
if (!arguments.length) return width;
3900
width = _;
3901
return chart;
3902
};
3903
3904
chart.height = function(_) {
3905
if (!arguments.length) return height;
3906
height = _;
3907
return chart;
3908
};
3909
3910
chart.xScale = function(_) {
3911
if (!arguments.length) return x;
3912
x = _;
3913
return chart;
3914
};
3915
3916
chart.yScale = function(_) {
3917
if (!arguments.length) return y;
3918
y = _;
3919
return chart;
3920
};
3921
3922
chart.xDomain = function(_) {
3923
if (!arguments.length) return xDomain;
3924
xDomain = _;
3925
return chart;
3926
};
3927
3928
chart.yDomain = function(_) {
3929
if (!arguments.length) return yDomain;
3930
yDomain = _;
3931
return chart;
3932
};
3933
3934
chart.xRange = function(_) {
3935
if (!arguments.length) return xRange;
3936
xRange = _;
3937
return chart;
3938
};
3939
3940
chart.yRange = function(_) {
3941
if (!arguments.length) return yRange;
3942
yRange = _;
3943
return chart;
3944
};
3945
3946
chart.forceX = function(_) {
3947
if (!arguments.length) return forceX;
3948
forceX = _;
3949
return chart;
3950
};
3951
3952
chart.forceY = function(_) {
3953
if (!arguments.length) return forceY;
3954
forceY = _;
3955
return chart;
3956
};
3957
3958
chart.padData = function(_) {
3959
if (!arguments.length) return padData;
3960
padData = _;
3961
return chart;
3962
};
3963
3964
chart.clipEdge = function(_) {
3965
if (!arguments.length) return clipEdge;
3966
clipEdge = _;
3967
return chart;
3968
};
3969
3970
chart.color = function(_) {
3971
if (!arguments.length) return color;
3972
color = nv.utils.getColor(_);
3973
return chart;
3974
};
3975
3976
chart.id = function(_) {
3977
if (!arguments.length) return id;
3978
id = _;
3979
return chart;
3980
};
3981
3982
chart.interactive = function(_) {
3983
if(!arguments.length) return interactive;
3984
interactive = false;
3985
return chart;
3986
};
3987
3988
//============================================================
3989
3990
3991
return chart;
3992
}
3993
3994
nv.models.historicalBarChart = function() {
3995
"use strict";
3996
//============================================================
3997
// Public Variables with Default Settings
3998
//------------------------------------------------------------
3999
4000
var bars = nv.models.historicalBar()
4001
, xAxis = nv.models.axis()
4002
, yAxis = nv.models.axis()
4003
, legend = nv.models.legend()
4004
;
4005
4006
4007
var margin = {top: 30, right: 90, bottom: 50, left: 90}
4008
, color = nv.utils.defaultColor()
4009
, width = null
4010
, height = null
4011
, showLegend = false
4012
, showXAxis = true
4013
, showYAxis = true
4014
, rightAlignYAxis = false
4015
, tooltips = true
4016
, tooltip = function(key, x, y, e, graph) {
4017
return '<h3>' + key + '</h3>' +
4018
'<p>' + y + ' at ' + x + '</p>'
4019
}
4020
, x
4021
, y
4022
, state = {}
4023
, defaultState = null
4024
, noData = 'No Data Available.'
4025
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
4026
, transitionDuration = 250
4027
;
4028
4029
xAxis
4030
.orient('bottom')
4031
.tickPadding(7)
4032
;
4033
yAxis
4034
.orient( (rightAlignYAxis) ? 'right' : 'left')
4035
;
4036
4037
//============================================================
4038
4039
4040
//============================================================
4041
// Private Variables
4042
//------------------------------------------------------------
4043
4044
var showTooltip = function(e, offsetElement) {
4045
4046
// New addition to calculate position if SVG is scaled with viewBox, may move TODO: consider implementing everywhere else
4047
if (offsetElement) {
4048
var svg = d3.select(offsetElement).select('svg');
4049
var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
4050
if (viewBox) {
4051
viewBox = viewBox.split(' ');
4052
var ratio = parseInt(svg.style('width')) / viewBox[2];
4053
e.pos[0] = e.pos[0] * ratio;
4054
e.pos[1] = e.pos[1] * ratio;
4055
}
4056
}
4057
4058
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
4059
top = e.pos[1] + ( offsetElement.offsetTop || 0),
4060
x = xAxis.tickFormat()(bars.x()(e.point, e.pointIndex)),
4061
y = yAxis.tickFormat()(bars.y()(e.point, e.pointIndex)),
4062
content = tooltip(e.series.key, x, y, e, chart);
4063
4064
nv.tooltip.show([left, top], content, null, null, offsetElement);
4065
};
4066
4067
//============================================================
4068
4069
4070
function chart(selection) {
4071
selection.each(function(data) {
4072
var container = d3.select(this),
4073
that = this;
4074
4075
var availableWidth = (width || parseInt(container.style('width')) || 960)
4076
- margin.left - margin.right,
4077
availableHeight = (height || parseInt(container.style('height')) || 400)
4078
- margin.top - margin.bottom;
4079
4080
4081
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
4082
chart.container = this;
4083
4084
//set state.disabled
4085
state.disabled = data.map(function(d) { return !!d.disabled });
4086
4087
if (!defaultState) {
4088
var key;
4089
defaultState = {};
4090
for (key in state) {
4091
if (state[key] instanceof Array)
4092
defaultState[key] = state[key].slice(0);
4093
else
4094
defaultState[key] = state[key];
4095
}
4096
}
4097
4098
//------------------------------------------------------------
4099
// Display noData message if there's nothing to show.
4100
4101
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
4102
var noDataText = container.selectAll('.nv-noData').data([noData]);
4103
4104
noDataText.enter().append('text')
4105
.attr('class', 'nvd3 nv-noData')
4106
.attr('dy', '-.7em')
4107
.style('text-anchor', 'middle');
4108
4109
noDataText
4110
.attr('x', margin.left + availableWidth / 2)
4111
.attr('y', margin.top + availableHeight / 2)
4112
.text(function(d) { return d });
4113
4114
return chart;
4115
} else {
4116
container.selectAll('.nv-noData').remove();
4117
}
4118
4119
//------------------------------------------------------------
4120
4121
4122
//------------------------------------------------------------
4123
// Setup Scales
4124
4125
x = bars.xScale();
4126
y = bars.yScale();
4127
4128
//------------------------------------------------------------
4129
4130
4131
//------------------------------------------------------------
4132
// Setup containers and skeleton of chart
4133
4134
var wrap = container.selectAll('g.nv-wrap.nv-historicalBarChart').data([data]);
4135
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-historicalBarChart').append('g');
4136
var g = wrap.select('g');
4137
4138
gEnter.append('g').attr('class', 'nv-x nv-axis');
4139
gEnter.append('g').attr('class', 'nv-y nv-axis');
4140
gEnter.append('g').attr('class', 'nv-barsWrap');
4141
gEnter.append('g').attr('class', 'nv-legendWrap');
4142
4143
//------------------------------------------------------------
4144
4145
4146
//------------------------------------------------------------
4147
// Legend
4148
4149
if (showLegend) {
4150
legend.width(availableWidth);
4151
4152
g.select('.nv-legendWrap')
4153
.datum(data)
4154
.call(legend);
4155
4156
if ( margin.top != legend.height()) {
4157
margin.top = legend.height();
4158
availableHeight = (height || parseInt(container.style('height')) || 400)
4159
- margin.top - margin.bottom;
4160
}
4161
4162
wrap.select('.nv-legendWrap')
4163
.attr('transform', 'translate(0,' + (-margin.top) +')')
4164
}
4165
4166
//------------------------------------------------------------
4167
4168
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4169
4170
if (rightAlignYAxis) {
4171
g.select(".nv-y.nv-axis")
4172
.attr("transform", "translate(" + availableWidth + ",0)");
4173
}
4174
4175
4176
//------------------------------------------------------------
4177
// Main Chart Component(s)
4178
4179
bars
4180
.width(availableWidth)
4181
.height(availableHeight)
4182
.color(data.map(function(d,i) {
4183
return d.color || color(d, i);
4184
}).filter(function(d,i) { return !data[i].disabled }));
4185
4186
4187
var barsWrap = g.select('.nv-barsWrap')
4188
.datum(data.filter(function(d) { return !d.disabled }))
4189
4190
barsWrap.transition().call(bars);
4191
4192
//------------------------------------------------------------
4193
4194
4195
//------------------------------------------------------------
4196
// Setup Axes
4197
4198
if (showXAxis) {
4199
xAxis
4200
.scale(x)
4201
.tickSize(-availableHeight, 0);
4202
4203
g.select('.nv-x.nv-axis')
4204
.attr('transform', 'translate(0,' + y.range()[0] + ')');
4205
g.select('.nv-x.nv-axis')
4206
.transition()
4207
.call(xAxis);
4208
}
4209
4210
if (showYAxis) {
4211
yAxis
4212
.scale(y)
4213
.ticks( availableHeight / 36 )
4214
.tickSize( -availableWidth, 0);
4215
4216
g.select('.nv-y.nv-axis')
4217
.transition()
4218
.call(yAxis);
4219
}
4220
//------------------------------------------------------------
4221
4222
4223
//============================================================
4224
// Event Handling/Dispatching (in chart's scope)
4225
//------------------------------------------------------------
4226
4227
legend.dispatch.on('legendClick', function(d,i) {
4228
d.disabled = !d.disabled;
4229
4230
if (!data.filter(function(d) { return !d.disabled }).length) {
4231
data.map(function(d) {
4232
d.disabled = false;
4233
wrap.selectAll('.nv-series').classed('disabled', false);
4234
return d;
4235
});
4236
}
4237
4238
state.disabled = data.map(function(d) { return !!d.disabled });
4239
dispatch.stateChange(state);
4240
4241
selection.transition().call(chart);
4242
});
4243
4244
legend.dispatch.on('legendDblclick', function(d) {
4245
//Double clicking should always enable current series, and disabled all others.
4246
data.forEach(function(d) {
4247
d.disabled = true;
4248
});
4249
d.disabled = false;
4250
4251
state.disabled = data.map(function(d) { return !!d.disabled });
4252
dispatch.stateChange(state);
4253
chart.update();
4254
});
4255
4256
dispatch.on('tooltipShow', function(e) {
4257
if (tooltips) showTooltip(e, that.parentNode);
4258
});
4259
4260
4261
dispatch.on('changeState', function(e) {
4262
4263
if (typeof e.disabled !== 'undefined') {
4264
data.forEach(function(series,i) {
4265
series.disabled = e.disabled[i];
4266
});
4267
4268
state.disabled = e.disabled;
4269
}
4270
4271
selection.call(chart);
4272
});
4273
4274
//============================================================
4275
4276
});
4277
4278
return chart;
4279
}
4280
4281
4282
//============================================================
4283
// Event Handling/Dispatching (out of chart's scope)
4284
//------------------------------------------------------------
4285
4286
bars.dispatch.on('elementMouseover.tooltip', function(e) {
4287
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
4288
dispatch.tooltipShow(e);
4289
});
4290
4291
bars.dispatch.on('elementMouseout.tooltip', function(e) {
4292
dispatch.tooltipHide(e);
4293
});
4294
4295
dispatch.on('tooltipHide', function() {
4296
if (tooltips) nv.tooltip.cleanup();
4297
});
4298
4299
//============================================================
4300
4301
4302
//============================================================
4303
// Expose Public Variables
4304
//------------------------------------------------------------
4305
4306
// expose chart's sub-components
4307
chart.dispatch = dispatch;
4308
chart.bars = bars;
4309
chart.legend = legend;
4310
chart.xAxis = xAxis;
4311
chart.yAxis = yAxis;
4312
4313
d3.rebind(chart, bars, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale',
4314
'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id', 'interpolate','highlightPoint','clearHighlights', 'interactive');
4315
4316
chart.options = nv.utils.optionsFunc.bind(chart);
4317
4318
chart.margin = function(_) {
4319
if (!arguments.length) return margin;
4320
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4321
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4322
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4323
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4324
return chart;
4325
};
4326
4327
chart.width = function(_) {
4328
if (!arguments.length) return width;
4329
width = _;
4330
return chart;
4331
};
4332
4333
chart.height = function(_) {
4334
if (!arguments.length) return height;
4335
height = _;
4336
return chart;
4337
};
4338
4339
chart.color = function(_) {
4340
if (!arguments.length) return color;
4341
color = nv.utils.getColor(_);
4342
legend.color(color);
4343
return chart;
4344
};
4345
4346
chart.showLegend = function(_) {
4347
if (!arguments.length) return showLegend;
4348
showLegend = _;
4349
return chart;
4350
};
4351
4352
chart.showXAxis = function(_) {
4353
if (!arguments.length) return showXAxis;
4354
showXAxis = _;
4355
return chart;
4356
};
4357
4358
chart.showYAxis = function(_) {
4359
if (!arguments.length) return showYAxis;
4360
showYAxis = _;
4361
return chart;
4362
};
4363
4364
chart.rightAlignYAxis = function(_) {
4365
if(!arguments.length) return rightAlignYAxis;
4366
rightAlignYAxis = _;
4367
yAxis.orient( (_) ? 'right' : 'left');
4368
return chart;
4369
};
4370
4371
chart.tooltips = function(_) {
4372
if (!arguments.length) return tooltips;
4373
tooltips = _;
4374
return chart;
4375
};
4376
4377
chart.tooltipContent = function(_) {
4378
if (!arguments.length) return tooltip;
4379
tooltip = _;
4380
return chart;
4381
};
4382
4383
chart.state = function(_) {
4384
if (!arguments.length) return state;
4385
state = _;
4386
return chart;
4387
};
4388
4389
chart.defaultState = function(_) {
4390
if (!arguments.length) return defaultState;
4391
defaultState = _;
4392
return chart;
4393
};
4394
4395
chart.noData = function(_) {
4396
if (!arguments.length) return noData;
4397
noData = _;
4398
return chart;
4399
};
4400
4401
chart.transitionDuration = function(_) {
4402
if (!arguments.length) return transitionDuration;
4403
transitionDuration = _;
4404
return chart;
4405
};
4406
4407
//============================================================
4408
4409
4410
return chart;
4411
}
4412
nv.models.indentedTree = function() {
4413
"use strict";
4414
//============================================================
4415
// Public Variables with Default Settings
4416
//------------------------------------------------------------
4417
4418
var margin = {top: 0, right: 0, bottom: 0, left: 0} //TODO: implement, maybe as margin on the containing div
4419
, width = 960
4420
, height = 500
4421
, color = nv.utils.defaultColor()
4422
, id = Math.floor(Math.random() * 10000)
4423
, header = true
4424
, filterZero = false
4425
, noData = "No Data Available."
4426
, childIndent = 20
4427
, columns = [{key:'key', label: 'Name', type:'text'}] //TODO: consider functions like chart.addColumn, chart.removeColumn, instead of a block like this
4428
, tableClass = null
4429
, iconOpen = 'images/grey-plus.png' //TODO: consider removing this and replacing with a '+' or '-' unless user defines images
4430
, iconClose = 'images/grey-minus.png'
4431
, dispatch = d3.dispatch('elementClick', 'elementDblclick', 'elementMouseover', 'elementMouseout')
4432
, getUrl = function(d) { return d.url }
4433
;
4434
4435
//============================================================
4436
4437
var idx = 0;
4438
4439
function chart(selection) {
4440
selection.each(function(data) {
4441
var depth = 1,
4442
container = d3.select(this);
4443
4444
var tree = d3.layout.tree()
4445
.children(function(d) { return d.values })
4446
.size([height, childIndent]); //Not sure if this is needed now that the result is HTML
4447
4448
chart.update = function() { container.transition().duration(600).call(chart) };
4449
4450
4451
//------------------------------------------------------------
4452
// Display No Data message if there's nothing to show.
4453
if (!data[0]) data[0] = {key: noData};
4454
4455
//------------------------------------------------------------
4456
4457
4458
var nodes = tree.nodes(data[0]);
4459
4460
// nodes.map(function(d) {
4461
// d.id = i++;
4462
// })
4463
4464
//------------------------------------------------------------
4465
// Setup containers and skeleton of chart
4466
4467
var wrap = d3.select(this).selectAll('div').data([[nodes]]);
4468
var wrapEnter = wrap.enter().append('div').attr('class', 'nvd3 nv-wrap nv-indentedtree');
4469
var tableEnter = wrapEnter.append('table');
4470
var table = wrap.select('table').attr('width', '100%').attr('class', tableClass);
4471
4472
//------------------------------------------------------------
4473
4474
4475
if (header) {
4476
var thead = tableEnter.append('thead');
4477
4478
var theadRow1 = thead.append('tr');
4479
4480
columns.forEach(function(column) {
4481
theadRow1
4482
.append('th')
4483
.attr('width', column.width ? column.width : '10%')
4484
.style('text-align', column.type == 'numeric' ? 'right' : 'left')
4485
.append('span')
4486
.text(column.label);
4487
});
4488
}
4489
4490
4491
var tbody = table.selectAll('tbody')
4492
.data(function(d) { return d });
4493
tbody.enter().append('tbody');
4494
4495
4496
4497
//compute max generations
4498
depth = d3.max(nodes, function(node) { return node.depth });
4499
tree.size([height, depth * childIndent]); //TODO: see if this is necessary at all
4500
4501
4502
// Update the nodes…
4503
var node = tbody.selectAll('tr')
4504
// .data(function(d) { return d; }, function(d) { return d.id || (d.id == ++i)});
4505
.data(function(d) { return d.filter(function(d) { return (filterZero && !d.children) ? filterZero(d) : true; } )}, function(d,i) { return d.id || (d.id || ++idx)});
4506
//.style('display', 'table-row'); //TODO: see if this does anything
4507
4508
node.exit().remove();
4509
4510
node.select('img.nv-treeicon')
4511
.attr('src', icon)
4512
.classed('folded', folded);
4513
4514
var nodeEnter = node.enter().append('tr');
4515
4516
4517
columns.forEach(function(column, index) {
4518
4519
var nodeName = nodeEnter.append('td')
4520
.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
4521
.style('text-align', column.type == 'numeric' ? 'right' : 'left');
4522
4523
4524
if (index == 0) {
4525
nodeName.append('img')
4526
.classed('nv-treeicon', true)
4527
.classed('nv-folded', folded)
4528
.attr('src', icon)
4529
.style('width', '14px')
4530
.style('height', '14px')
4531
.style('padding', '0 1px')
4532
.style('display', function(d) { return icon(d) ? 'inline-block' : 'none'; })
4533
.on('click', click);
4534
}
4535
4536
4537
nodeName.each(function(d) {
4538
if (!index && getUrl(d))
4539
d3.select(this)
4540
.append('a')
4541
.attr('href',getUrl)
4542
.attr('class', d3.functor(column.classes))
4543
.append('span')
4544
else
4545
d3.select(this)
4546
.append('span')
4547
4548
d3.select(this).select('span')
4549
.attr('class', d3.functor(column.classes) )
4550
.text(function(d) { return column.format ? column.format(d) :
4551
(d[column.key] || '-') });
4552
});
4553
4554
if (column.showCount) {
4555
nodeName.append('span')
4556
.attr('class', 'nv-childrenCount');
4557
4558
node.selectAll('span.nv-childrenCount').text(function(d) {
4559
return ((d.values && d.values.length) || (d._values && d._values.length)) ? //If this is a parent
4560
'(' + ((d.values && (d.values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length)) //If children are in values check its children and filter
4561
|| (d._values && d._values.filter(function(d) { return filterZero ? filterZero(d) : true; }).length) //Otherwise, do the same, but with the other name, _values...
4562
|| 0) + ')' //This is the catch-all in case there are no children after a filter
4563
: '' //If this is not a parent, just give an empty string
4564
});
4565
}
4566
4567
// if (column.click)
4568
// nodeName.select('span').on('click', column.click);
4569
4570
});
4571
4572
node
4573
.order()
4574
.on('click', function(d) {
4575
dispatch.elementClick({
4576
row: this, //TODO: decide whether or not this should be consistent with scatter/line events or should be an html link (a href)
4577
data: d,
4578
pos: [d.x, d.y]
4579
});
4580
})
4581
.on('dblclick', function(d) {
4582
dispatch.elementDblclick({
4583
row: this,
4584
data: d,
4585
pos: [d.x, d.y]
4586
});
4587
})
4588
.on('mouseover', function(d) {
4589
dispatch.elementMouseover({
4590
row: this,
4591
data: d,
4592
pos: [d.x, d.y]
4593
});
4594
})
4595
.on('mouseout', function(d) {
4596
dispatch.elementMouseout({
4597
row: this,
4598
data: d,
4599
pos: [d.x, d.y]
4600
});
4601
});
4602
4603
4604
4605
4606
// Toggle children on click.
4607
function click(d, _, unshift) {
4608
d3.event.stopPropagation();
4609
4610
if(d3.event.shiftKey && !unshift) {
4611
//If you shift-click, it'll toggle fold all the children, instead of itself
4612
d3.event.shiftKey = false;
4613
d.values && d.values.forEach(function(node){
4614
if (node.values || node._values) {
4615
click(node, 0, true);
4616
}
4617
});
4618
return true;
4619
}
4620
if(!hasChildren(d)) {
4621
//download file
4622
//window.location.href = d.url;
4623
return true;
4624
}
4625
if (d.values) {
4626
d._values = d.values;
4627
d.values = null;
4628
} else {
4629
d.values = d._values;
4630
d._values = null;
4631
}
4632
chart.update();
4633
}
4634
4635
4636
function icon(d) {
4637
return (d._values && d._values.length) ? iconOpen : (d.values && d.values.length) ? iconClose : '';
4638
}
4639
4640
function folded(d) {
4641
return (d._values && d._values.length);
4642
}
4643
4644
function hasChildren(d) {
4645
var values = d.values || d._values;
4646
4647
return (values && values.length);
4648
}
4649
4650
4651
});
4652
4653
return chart;
4654
}
4655
4656
4657
//============================================================
4658
// Expose Public Variables
4659
//------------------------------------------------------------
4660
chart.options = nv.utils.optionsFunc.bind(chart);
4661
4662
chart.margin = function(_) {
4663
if (!arguments.length) return margin;
4664
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4665
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4666
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4667
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4668
return chart;
4669
};
4670
4671
chart.width = function(_) {
4672
if (!arguments.length) return width;
4673
width = _;
4674
return chart;
4675
};
4676
4677
chart.height = function(_) {
4678
if (!arguments.length) return height;
4679
height = _;
4680
return chart;
4681
};
4682
4683
chart.color = function(_) {
4684
if (!arguments.length) return color;
4685
color = nv.utils.getColor(_);
4686
scatter.color(color);
4687
return chart;
4688
};
4689
4690
chart.id = function(_) {
4691
if (!arguments.length) return id;
4692
id = _;
4693
return chart;
4694
};
4695
4696
chart.header = function(_) {
4697
if (!arguments.length) return header;
4698
header = _;
4699
return chart;
4700
};
4701
4702
chart.noData = function(_) {
4703
if (!arguments.length) return noData;
4704
noData = _;
4705
return chart;
4706
};
4707
4708
chart.filterZero = function(_) {
4709
if (!arguments.length) return filterZero;
4710
filterZero = _;
4711
return chart;
4712
};
4713
4714
chart.columns = function(_) {
4715
if (!arguments.length) return columns;
4716
columns = _;
4717
return chart;
4718
};
4719
4720
chart.tableClass = function(_) {
4721
if (!arguments.length) return tableClass;
4722
tableClass = _;
4723
return chart;
4724
};
4725
4726
chart.iconOpen = function(_){
4727
if (!arguments.length) return iconOpen;
4728
iconOpen = _;
4729
return chart;
4730
}
4731
4732
chart.iconClose = function(_){
4733
if (!arguments.length) return iconClose;
4734
iconClose = _;
4735
return chart;
4736
}
4737
4738
chart.getUrl = function(_){
4739
if (!arguments.length) return getUrl;
4740
getUrl = _;
4741
return chart;
4742
}
4743
4744
//============================================================
4745
4746
4747
return chart;
4748
};nv.models.legend = function() {
4749
"use strict";
4750
//============================================================
4751
// Public Variables with Default Settings
4752
//------------------------------------------------------------
4753
4754
var margin = {top: 5, right: 0, bottom: 5, left: 0}
4755
, width = 400
4756
, height = 20
4757
, getKey = function(d) { return d.key }
4758
, color = nv.utils.defaultColor()
4759
, align = true
4760
, rightAlign = true
4761
, updateState = true //If true, legend will update data.disabled and trigger a 'stateChange' dispatch.
4762
, radioButtonMode = false //If true, clicking legend items will cause it to behave like a radio button. (only one can be selected at a time)
4763
, dispatch = d3.dispatch('legendClick', 'legendDblclick', 'legendMouseover', 'legendMouseout', 'stateChange')
4764
;
4765
4766
//============================================================
4767
4768
4769
function chart(selection) {
4770
selection.each(function(data) {
4771
var availableWidth = width - margin.left - margin.right,
4772
container = d3.select(this);
4773
4774
4775
//------------------------------------------------------------
4776
// Setup containers and skeleton of chart
4777
4778
var wrap = container.selectAll('g.nv-legend').data([data]);
4779
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-legend').append('g');
4780
var g = wrap.select('g');
4781
4782
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
4783
4784
//------------------------------------------------------------
4785
4786
4787
var series = g.selectAll('.nv-series')
4788
.data(function(d) { return d });
4789
var seriesEnter = series.enter().append('g').attr('class', 'nv-series')
4790
.on('mouseover', function(d,i) {
4791
dispatch.legendMouseover(d,i); //TODO: Make consistent with other event objects
4792
})
4793
.on('mouseout', function(d,i) {
4794
dispatch.legendMouseout(d,i);
4795
})
4796
.on('click', function(d,i) {
4797
dispatch.legendClick(d,i);
4798
if (updateState) {
4799
if (radioButtonMode) {
4800
//Radio button mode: set every series to disabled,
4801
// and enable the clicked series.
4802
data.forEach(function(series) { series.disabled = true});
4803
d.disabled = false;
4804
}
4805
else {
4806
d.disabled = !d.disabled;
4807
if (data.every(function(series) { return series.disabled})) {
4808
//the default behavior of NVD3 legends is, if every single series
4809
// is disabled, turn all series' back on.
4810
data.forEach(function(series) { series.disabled = false});
4811
}
4812
}
4813
dispatch.stateChange({
4814
disabled: data.map(function(d) { return !!d.disabled })
4815
});
4816
}
4817
})
4818
.on('dblclick', function(d,i) {
4819
dispatch.legendDblclick(d,i);
4820
if (updateState) {
4821
//the default behavior of NVD3 legends, when double clicking one,
4822
// is to set all other series' to false, and make the double clicked series enabled.
4823
data.forEach(function(series) {
4824
series.disabled = true;
4825
});
4826
d.disabled = false;
4827
dispatch.stateChange({
4828
disabled: data.map(function(d) { return !!d.disabled })
4829
});
4830
}
4831
});
4832
seriesEnter.append('circle')
4833
.style('stroke-width', 2)
4834
.attr('class','nv-legend-symbol')
4835
.attr('r', 5);
4836
seriesEnter.append('text')
4837
.attr('text-anchor', 'start')
4838
.attr('class','nv-legend-text')
4839
.attr('dy', '.32em')
4840
.attr('dx', '8');
4841
series.classed('disabled', function(d) { return d.disabled });
4842
series.exit().remove();
4843
series.select('circle')
4844
.style('fill', function(d,i) { return d.color || color(d,i)})
4845
.style('stroke', function(d,i) { return d.color || color(d, i) });
4846
series.select('text').text(getKey);
4847
4848
4849
//TODO: implement fixed-width and max-width options (max-width is especially useful with the align option)
4850
4851
// NEW ALIGNING CODE, TODO: clean up
4852
if (align) {
4853
4854
var seriesWidths = [];
4855
series.each(function(d,i) {
4856
var legendText = d3.select(this).select('text');
4857
var nodeTextLength;
4858
try {
4859
nodeTextLength = legendText.node().getComputedTextLength();
4860
}
4861
catch(e) {
4862
nodeTextLength = nv.utils.calcApproxTextWidth(legendText);
4863
}
4864
4865
seriesWidths.push(nodeTextLength + 28); // 28 is ~ the width of the circle plus some padding
4866
});
4867
4868
var seriesPerRow = 0;
4869
var legendWidth = 0;
4870
var columnWidths = [];
4871
4872
while ( legendWidth < availableWidth && seriesPerRow < seriesWidths.length) {
4873
columnWidths[seriesPerRow] = seriesWidths[seriesPerRow];
4874
legendWidth += seriesWidths[seriesPerRow++];
4875
}
4876
if (seriesPerRow === 0) seriesPerRow = 1; //minimum of one series per row
4877
4878
4879
while ( legendWidth > availableWidth && seriesPerRow > 1 ) {
4880
columnWidths = [];
4881
seriesPerRow--;
4882
4883
for (var k = 0; k < seriesWidths.length; k++) {
4884
if (seriesWidths[k] > (columnWidths[k % seriesPerRow] || 0) )
4885
columnWidths[k % seriesPerRow] = seriesWidths[k];
4886
}
4887
4888
legendWidth = columnWidths.reduce(function(prev, cur, index, array) {
4889
return prev + cur;
4890
});
4891
}
4892
4893
var xPositions = [];
4894
for (var i = 0, curX = 0; i < seriesPerRow; i++) {
4895
xPositions[i] = curX;
4896
curX += columnWidths[i];
4897
}
4898
4899
series
4900
.attr('transform', function(d, i) {
4901
return 'translate(' + xPositions[i % seriesPerRow] + ',' + (5 + Math.floor(i / seriesPerRow) * 20) + ')';
4902
});
4903
4904
//position legend as far right as possible within the total width
4905
if (rightAlign) {
4906
g.attr('transform', 'translate(' + (width - margin.right - legendWidth) + ',' + margin.top + ')');
4907
}
4908
else {
4909
g.attr('transform', 'translate(0' + ',' + margin.top + ')');
4910
}
4911
4912
height = margin.top + margin.bottom + (Math.ceil(seriesWidths.length / seriesPerRow) * 20);
4913
4914
} else {
4915
4916
var ypos = 5,
4917
newxpos = 5,
4918
maxwidth = 0,
4919
xpos;
4920
series
4921
.attr('transform', function(d, i) {
4922
var length = d3.select(this).select('text').node().getComputedTextLength() + 28;
4923
xpos = newxpos;
4924
4925
if (width < margin.left + margin.right + xpos + length) {
4926
newxpos = xpos = 5;
4927
ypos += 20;
4928
}
4929
4930
newxpos += length;
4931
if (newxpos > maxwidth) maxwidth = newxpos;
4932
4933
return 'translate(' + xpos + ',' + ypos + ')';
4934
});
4935
4936
//position legend as far right as possible within the total width
4937
g.attr('transform', 'translate(' + (width - margin.right - maxwidth) + ',' + margin.top + ')');
4938
4939
height = margin.top + margin.bottom + ypos + 15;
4940
4941
}
4942
4943
});
4944
4945
return chart;
4946
}
4947
4948
4949
//============================================================
4950
// Expose Public Variables
4951
//------------------------------------------------------------
4952
4953
chart.dispatch = dispatch;
4954
chart.options = nv.utils.optionsFunc.bind(chart);
4955
4956
chart.margin = function(_) {
4957
if (!arguments.length) return margin;
4958
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
4959
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
4960
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
4961
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
4962
return chart;
4963
};
4964
4965
chart.width = function(_) {
4966
if (!arguments.length) return width;
4967
width = _;
4968
return chart;
4969
};
4970
4971
chart.height = function(_) {
4972
if (!arguments.length) return height;
4973
height = _;
4974
return chart;
4975
};
4976
4977
chart.key = function(_) {
4978
if (!arguments.length) return getKey;
4979
getKey = _;
4980
return chart;
4981
};
4982
4983
chart.color = function(_) {
4984
if (!arguments.length) return color;
4985
color = nv.utils.getColor(_);
4986
return chart;
4987
};
4988
4989
chart.align = function(_) {
4990
if (!arguments.length) return align;
4991
align = _;
4992
return chart;
4993
};
4994
4995
chart.rightAlign = function(_) {
4996
if (!arguments.length) return rightAlign;
4997
rightAlign = _;
4998
return chart;
4999
};
5000
5001
chart.updateState = function(_) {
5002
if (!arguments.length) return updateState;
5003
updateState = _;
5004
return chart;
5005
};
5006
5007
chart.radioButtonMode = function(_) {
5008
if (!arguments.length) return radioButtonMode;
5009
radioButtonMode = _;
5010
return chart;
5011
};
5012
5013
//============================================================
5014
5015
5016
return chart;
5017
}
5018
5019
nv.models.line = function() {
5020
"use strict";
5021
//============================================================
5022
// Public Variables with Default Settings
5023
//------------------------------------------------------------
5024
5025
var scatter = nv.models.scatter()
5026
;
5027
5028
var margin = {top: 0, right: 0, bottom: 0, left: 0}
5029
, width = 960
5030
, height = 500
5031
, color = nv.utils.defaultColor() // a function that returns a color
5032
, getX = function(d) { return d.x } // accessor to get the x value from a data point
5033
, getY = function(d) { return d.y } // accessor to get the y value from a data point
5034
, 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
5035
, isArea = function(d) { return d.area } // decides if a line is an area or just a line
5036
, clipEdge = false // if true, masks lines within x and y scale
5037
, x //can be accessed via chart.xScale()
5038
, y //can be accessed via chart.yScale()
5039
, interpolate = "linear" // controls the line interpolation
5040
;
5041
5042
scatter
5043
.size(16) // default size
5044
.sizeDomain([16,256]) //set to speed up calculation, needs to be unset if there is a custom size accessor
5045
;
5046
5047
//============================================================
5048
5049
5050
//============================================================
5051
// Private Variables
5052
//------------------------------------------------------------
5053
5054
var x0, y0 //used to store previous scales
5055
;
5056
5057
//============================================================
5058
5059
5060
function chart(selection) {
5061
selection.each(function(data) {
5062
var availableWidth = width - margin.left - margin.right,
5063
availableHeight = height - margin.top - margin.bottom,
5064
container = d3.select(this);
5065
5066
//------------------------------------------------------------
5067
// Setup Scales
5068
5069
x = scatter.xScale();
5070
y = scatter.yScale();
5071
5072
x0 = x0 || x;
5073
y0 = y0 || y;
5074
5075
//------------------------------------------------------------
5076
5077
5078
//------------------------------------------------------------
5079
// Setup containers and skeleton of chart
5080
5081
var wrap = container.selectAll('g.nv-wrap.nv-line').data([data]);
5082
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-line');
5083
var defsEnter = wrapEnter.append('defs');
5084
var gEnter = wrapEnter.append('g');
5085
var g = wrap.select('g')
5086
5087
gEnter.append('g').attr('class', 'nv-groups');
5088
gEnter.append('g').attr('class', 'nv-scatterWrap');
5089
5090
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5091
5092
//------------------------------------------------------------
5093
5094
5095
5096
5097
scatter
5098
.width(availableWidth)
5099
.height(availableHeight)
5100
5101
var scatterWrap = wrap.select('.nv-scatterWrap');
5102
//.datum(data); // Data automatically trickles down from the wrap
5103
5104
scatterWrap.transition().call(scatter);
5105
5106
5107
5108
defsEnter.append('clipPath')
5109
.attr('id', 'nv-edge-clip-' + scatter.id())
5110
.append('rect');
5111
5112
wrap.select('#nv-edge-clip-' + scatter.id() + ' rect')
5113
.attr('width', availableWidth)
5114
.attr('height', availableHeight);
5115
5116
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5117
scatterWrap
5118
.attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + scatter.id() + ')' : '');
5119
5120
5121
5122
5123
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
5124
.data(function(d) { return d }, function(d) { return d.key });
5125
groups.enter().append('g')
5126
.style('stroke-opacity', 1e-6)
5127
.style('fill-opacity', 1e-6);
5128
groups.exit()
5129
.transition()
5130
.style('stroke-opacity', 1e-6)
5131
.style('fill-opacity', 1e-6)
5132
.remove();
5133
groups
5134
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
5135
.classed('hover', function(d) { return d.hover })
5136
.style('fill', function(d,i){ return color(d, i) })
5137
.style('stroke', function(d,i){ return color(d, i)});
5138
groups
5139
.transition()
5140
.style('stroke-opacity', 1)
5141
.style('fill-opacity', .5);
5142
5143
5144
5145
var areaPaths = groups.selectAll('path.nv-area')
5146
.data(function(d) { return isArea(d) ? [d] : [] }); // this is done differently than lines because I need to check if series is an area
5147
areaPaths.enter().append('path')
5148
.attr('class', 'nv-area')
5149
.attr('d', function(d) {
5150
return d3.svg.area()
5151
.interpolate(interpolate)
5152
.defined(defined)
5153
.x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5154
.y0(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5155
.y1(function(d,i) { return y0( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5156
//.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5157
.apply(this, [d.values])
5158
});
5159
groups.exit().selectAll('path.nv-area')
5160
.remove();
5161
5162
areaPaths
5163
.transition()
5164
.attr('d', function(d) {
5165
return d3.svg.area()
5166
.interpolate(interpolate)
5167
.defined(defined)
5168
.x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5169
.y0(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5170
.y1(function(d,i) { return y( y.domain()[0] <= 0 ? y.domain()[1] >= 0 ? 0 : y.domain()[1] : y.domain()[0] ) })
5171
//.y1(function(d,i) { return y0(0) }) //assuming 0 is within y domain.. may need to tweak this
5172
.apply(this, [d.values])
5173
});
5174
5175
5176
5177
var linePaths = groups.selectAll('path.nv-line')
5178
.data(function(d) { return [d.values] });
5179
linePaths.enter().append('path')
5180
.attr('class', 'nv-line')
5181
.attr('d',
5182
d3.svg.line()
5183
.interpolate(interpolate)
5184
.defined(defined)
5185
.x(function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
5186
.y(function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
5187
);
5188
groups.exit().selectAll('path.nv-line')
5189
.transition()
5190
.attr('d',
5191
d3.svg.line()
5192
.interpolate(interpolate)
5193
.defined(defined)
5194
.x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5195
.y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5196
);
5197
linePaths
5198
.transition()
5199
.attr('d',
5200
d3.svg.line()
5201
.interpolate(interpolate)
5202
.defined(defined)
5203
.x(function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
5204
.y(function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
5205
);
5206
5207
5208
5209
//store old scales for use in transitions on update
5210
x0 = x.copy();
5211
y0 = y.copy();
5212
5213
});
5214
5215
return chart;
5216
}
5217
5218
5219
//============================================================
5220
// Expose Public Variables
5221
//------------------------------------------------------------
5222
5223
chart.dispatch = scatter.dispatch;
5224
chart.scatter = scatter;
5225
5226
d3.rebind(chart, scatter, 'id', 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
5227
'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi', 'clipRadius', 'padData','highlightPoint','clearHighlights');
5228
5229
chart.options = nv.utils.optionsFunc.bind(chart);
5230
5231
chart.margin = function(_) {
5232
if (!arguments.length) return margin;
5233
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5234
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5235
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5236
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5237
return chart;
5238
};
5239
5240
chart.width = function(_) {
5241
if (!arguments.length) return width;
5242
width = _;
5243
return chart;
5244
};
5245
5246
chart.height = function(_) {
5247
if (!arguments.length) return height;
5248
height = _;
5249
return chart;
5250
};
5251
5252
chart.x = function(_) {
5253
if (!arguments.length) return getX;
5254
getX = _;
5255
scatter.x(_);
5256
return chart;
5257
};
5258
5259
chart.y = function(_) {
5260
if (!arguments.length) return getY;
5261
getY = _;
5262
scatter.y(_);
5263
return chart;
5264
};
5265
5266
chart.clipEdge = function(_) {
5267
if (!arguments.length) return clipEdge;
5268
clipEdge = _;
5269
return chart;
5270
};
5271
5272
chart.color = function(_) {
5273
if (!arguments.length) return color;
5274
color = nv.utils.getColor(_);
5275
scatter.color(color);
5276
return chart;
5277
};
5278
5279
chart.interpolate = function(_) {
5280
if (!arguments.length) return interpolate;
5281
interpolate = _;
5282
return chart;
5283
};
5284
5285
chart.defined = function(_) {
5286
if (!arguments.length) return defined;
5287
defined = _;
5288
return chart;
5289
};
5290
5291
chart.isArea = function(_) {
5292
if (!arguments.length) return isArea;
5293
isArea = d3.functor(_);
5294
return chart;
5295
};
5296
5297
//============================================================
5298
5299
5300
return chart;
5301
}
5302
5303
nv.models.lineChart = function() {
5304
"use strict";
5305
//============================================================
5306
// Public Variables with Default Settings
5307
//------------------------------------------------------------
5308
5309
var lines = nv.models.line()
5310
, xAxis = nv.models.axis()
5311
, yAxis = nv.models.axis()
5312
, legend = nv.models.legend()
5313
, interactiveLayer = nv.interactiveGuideline()
5314
;
5315
5316
var margin = {top: 30, right: 20, bottom: 50, left: 60}
5317
, color = nv.utils.defaultColor()
5318
, width = null
5319
, height = null
5320
, showLegend = true
5321
, showXAxis = true
5322
, showYAxis = true
5323
, rightAlignYAxis = false
5324
, useInteractiveGuideline = false
5325
, tooltips = true
5326
, tooltip = function(key, x, y, e, graph) {
5327
return '<h3>' + key + '</h3>' +
5328
'<p>' + y + ' at ' + x + '</p>'
5329
}
5330
, x
5331
, y
5332
, state = {}
5333
, defaultState = null
5334
, noData = 'No Data Available.'
5335
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5336
, transitionDuration = 250
5337
;
5338
5339
xAxis
5340
.orient('bottom')
5341
.tickPadding(7)
5342
;
5343
yAxis
5344
.orient((rightAlignYAxis) ? 'right' : 'left')
5345
;
5346
5347
//============================================================
5348
5349
5350
//============================================================
5351
// Private Variables
5352
//------------------------------------------------------------
5353
5354
var showTooltip = function(e, offsetElement) {
5355
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5356
top = e.pos[1] + ( offsetElement.offsetTop || 0),
5357
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5358
y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
5359
content = tooltip(e.series.key, x, y, e, chart);
5360
5361
nv.tooltip.show([left, top], content, null, null, offsetElement);
5362
};
5363
5364
//============================================================
5365
5366
5367
function chart(selection) {
5368
selection.each(function(data) {
5369
var container = d3.select(this),
5370
that = this;
5371
5372
var availableWidth = (width || parseInt(container.style('width')) || 960)
5373
- margin.left - margin.right,
5374
availableHeight = (height || parseInt(container.style('height')) || 400)
5375
- margin.top - margin.bottom;
5376
5377
5378
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
5379
chart.container = this;
5380
5381
//set state.disabled
5382
state.disabled = data.map(function(d) { return !!d.disabled });
5383
5384
5385
if (!defaultState) {
5386
var key;
5387
defaultState = {};
5388
for (key in state) {
5389
if (state[key] instanceof Array)
5390
defaultState[key] = state[key].slice(0);
5391
else
5392
defaultState[key] = state[key];
5393
}
5394
}
5395
5396
//------------------------------------------------------------
5397
// Display noData message if there's nothing to show.
5398
5399
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5400
var noDataText = container.selectAll('.nv-noData').data([noData]);
5401
5402
noDataText.enter().append('text')
5403
.attr('class', 'nvd3 nv-noData')
5404
.attr('dy', '-.7em')
5405
.style('text-anchor', 'middle');
5406
5407
noDataText
5408
.attr('x', margin.left + availableWidth / 2)
5409
.attr('y', margin.top + availableHeight / 2)
5410
.text(function(d) { return d });
5411
5412
return chart;
5413
} else {
5414
container.selectAll('.nv-noData').remove();
5415
}
5416
5417
//------------------------------------------------------------
5418
5419
5420
//------------------------------------------------------------
5421
// Setup Scales
5422
5423
x = lines.xScale();
5424
y = lines.yScale();
5425
5426
//------------------------------------------------------------
5427
5428
5429
//------------------------------------------------------------
5430
// Setup containers and skeleton of chart
5431
5432
var wrap = container.selectAll('g.nv-wrap.nv-lineChart').data([data]);
5433
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineChart').append('g');
5434
var g = wrap.select('g');
5435
5436
gEnter.append("rect").style("opacity",0);
5437
gEnter.append('g').attr('class', 'nv-x nv-axis');
5438
gEnter.append('g').attr('class', 'nv-y nv-axis');
5439
gEnter.append('g').attr('class', 'nv-linesWrap');
5440
gEnter.append('g').attr('class', 'nv-legendWrap');
5441
gEnter.append('g').attr('class', 'nv-interactive');
5442
5443
g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
5444
//------------------------------------------------------------
5445
// Legend
5446
5447
if (showLegend) {
5448
legend.width(availableWidth);
5449
5450
g.select('.nv-legendWrap')
5451
.datum(data)
5452
.call(legend);
5453
5454
if ( margin.top != legend.height()) {
5455
margin.top = legend.height();
5456
availableHeight = (height || parseInt(container.style('height')) || 400)
5457
- margin.top - margin.bottom;
5458
}
5459
5460
wrap.select('.nv-legendWrap')
5461
.attr('transform', 'translate(0,' + (-margin.top) +')')
5462
}
5463
5464
//------------------------------------------------------------
5465
5466
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5467
5468
if (rightAlignYAxis) {
5469
g.select(".nv-y.nv-axis")
5470
.attr("transform", "translate(" + availableWidth + ",0)");
5471
}
5472
5473
//------------------------------------------------------------
5474
// Main Chart Component(s)
5475
5476
5477
//------------------------------------------------------------
5478
//Set up interactive layer
5479
if (useInteractiveGuideline) {
5480
interactiveLayer
5481
.width(availableWidth)
5482
.height(availableHeight)
5483
.margin({left:margin.left, top:margin.top})
5484
.svgContainer(container)
5485
.xScale(x);
5486
wrap.select(".nv-interactive").call(interactiveLayer);
5487
}
5488
5489
5490
lines
5491
.width(availableWidth)
5492
.height(availableHeight)
5493
.color(data.map(function(d,i) {
5494
return d.color || color(d, i);
5495
}).filter(function(d,i) { return !data[i].disabled }));
5496
5497
5498
var linesWrap = g.select('.nv-linesWrap')
5499
.datum(data.filter(function(d) { return !d.disabled }))
5500
5501
linesWrap.transition().call(lines);
5502
5503
//------------------------------------------------------------
5504
5505
5506
//------------------------------------------------------------
5507
// Setup Axes
5508
5509
if (showXAxis) {
5510
xAxis
5511
.scale(x)
5512
.ticks( availableWidth / 100 )
5513
.tickSize(-availableHeight, 0);
5514
5515
g.select('.nv-x.nv-axis')
5516
.attr('transform', 'translate(0,' + y.range()[0] + ')');
5517
g.select('.nv-x.nv-axis')
5518
.transition()
5519
.call(xAxis);
5520
}
5521
5522
if (showYAxis) {
5523
yAxis
5524
.scale(y)
5525
.ticks( availableHeight / 36 )
5526
.tickSize( -availableWidth, 0);
5527
5528
g.select('.nv-y.nv-axis')
5529
.transition()
5530
.call(yAxis);
5531
}
5532
//------------------------------------------------------------
5533
5534
5535
//============================================================
5536
// Event Handling/Dispatching (in chart's scope)
5537
//------------------------------------------------------------
5538
5539
legend.dispatch.on('stateChange', function(newState) {
5540
state = newState;
5541
dispatch.stateChange(state);
5542
chart.update();
5543
});
5544
5545
interactiveLayer.dispatch.on('elementMousemove', function(e) {
5546
lines.clearHighlights();
5547
var singlePoint, pointIndex, pointXLocation, allData = [];
5548
data
5549
.filter(function(series, i) {
5550
series.seriesIndex = i;
5551
return !series.disabled;
5552
})
5553
.forEach(function(series,i) {
5554
pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
5555
lines.highlightPoint(i, pointIndex, true);
5556
var point = series.values[pointIndex];
5557
if (typeof point === 'undefined') return;
5558
if (typeof singlePoint === 'undefined') singlePoint = point;
5559
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
5560
allData.push({
5561
key: series.key,
5562
value: chart.y()(point, pointIndex),
5563
color: color(series,series.seriesIndex)
5564
});
5565
});
5566
5567
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
5568
interactiveLayer.tooltip
5569
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
5570
.chartContainer(that.parentNode)
5571
.enabled(tooltips)
5572
.valueFormatter(function(d,i) {
5573
return yAxis.tickFormat()(d);
5574
})
5575
.data(
5576
{
5577
value: xValue,
5578
series: allData
5579
}
5580
)();
5581
5582
interactiveLayer.renderGuideLine(pointXLocation);
5583
5584
});
5585
5586
interactiveLayer.dispatch.on("elementMouseout",function(e) {
5587
dispatch.tooltipHide();
5588
lines.clearHighlights();
5589
});
5590
5591
dispatch.on('tooltipShow', function(e) {
5592
if (tooltips) showTooltip(e, that.parentNode);
5593
});
5594
5595
5596
dispatch.on('changeState', function(e) {
5597
5598
if (typeof e.disabled !== 'undefined') {
5599
data.forEach(function(series,i) {
5600
series.disabled = e.disabled[i];
5601
});
5602
5603
state.disabled = e.disabled;
5604
}
5605
5606
chart.update();
5607
});
5608
5609
//============================================================
5610
5611
});
5612
5613
return chart;
5614
}
5615
5616
5617
//============================================================
5618
// Event Handling/Dispatching (out of chart's scope)
5619
//------------------------------------------------------------
5620
5621
lines.dispatch.on('elementMouseover.tooltip', function(e) {
5622
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
5623
dispatch.tooltipShow(e);
5624
});
5625
5626
lines.dispatch.on('elementMouseout.tooltip', function(e) {
5627
dispatch.tooltipHide(e);
5628
});
5629
5630
dispatch.on('tooltipHide', function() {
5631
if (tooltips) nv.tooltip.cleanup();
5632
});
5633
5634
//============================================================
5635
5636
5637
//============================================================
5638
// Expose Public Variables
5639
//------------------------------------------------------------
5640
5641
// expose chart's sub-components
5642
chart.dispatch = dispatch;
5643
chart.lines = lines;
5644
chart.legend = legend;
5645
chart.xAxis = xAxis;
5646
chart.yAxis = yAxis;
5647
chart.interactiveLayer = interactiveLayer;
5648
5649
d3.rebind(chart, lines, 'defined', 'isArea', 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange'
5650
, 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'useVoronoi','id', 'interpolate');
5651
5652
chart.options = nv.utils.optionsFunc.bind(chart);
5653
5654
chart.margin = function(_) {
5655
if (!arguments.length) return margin;
5656
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
5657
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
5658
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
5659
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
5660
return chart;
5661
};
5662
5663
chart.width = function(_) {
5664
if (!arguments.length) return width;
5665
width = _;
5666
return chart;
5667
};
5668
5669
chart.height = function(_) {
5670
if (!arguments.length) return height;
5671
height = _;
5672
return chart;
5673
};
5674
5675
chart.color = function(_) {
5676
if (!arguments.length) return color;
5677
color = nv.utils.getColor(_);
5678
legend.color(color);
5679
return chart;
5680
};
5681
5682
chart.showLegend = function(_) {
5683
if (!arguments.length) return showLegend;
5684
showLegend = _;
5685
return chart;
5686
};
5687
5688
chart.showXAxis = function(_) {
5689
if (!arguments.length) return showXAxis;
5690
showXAxis = _;
5691
return chart;
5692
};
5693
5694
chart.showYAxis = function(_) {
5695
if (!arguments.length) return showYAxis;
5696
showYAxis = _;
5697
return chart;
5698
};
5699
5700
chart.rightAlignYAxis = function(_) {
5701
if(!arguments.length) return rightAlignYAxis;
5702
rightAlignYAxis = _;
5703
yAxis.orient( (_) ? 'right' : 'left');
5704
return chart;
5705
};
5706
5707
chart.useInteractiveGuideline = function(_) {
5708
if(!arguments.length) return useInteractiveGuideline;
5709
useInteractiveGuideline = _;
5710
if (_ === true) {
5711
chart.interactive(false);
5712
chart.useVoronoi(false);
5713
}
5714
return chart;
5715
};
5716
5717
chart.tooltips = function(_) {
5718
if (!arguments.length) return tooltips;
5719
tooltips = _;
5720
return chart;
5721
};
5722
5723
chart.tooltipContent = function(_) {
5724
if (!arguments.length) return tooltip;
5725
tooltip = _;
5726
return chart;
5727
};
5728
5729
chart.state = function(_) {
5730
if (!arguments.length) return state;
5731
state = _;
5732
return chart;
5733
};
5734
5735
chart.defaultState = function(_) {
5736
if (!arguments.length) return defaultState;
5737
defaultState = _;
5738
return chart;
5739
};
5740
5741
chart.noData = function(_) {
5742
if (!arguments.length) return noData;
5743
noData = _;
5744
return chart;
5745
};
5746
5747
chart.transitionDuration = function(_) {
5748
if (!arguments.length) return transitionDuration;
5749
transitionDuration = _;
5750
return chart;
5751
};
5752
5753
//============================================================
5754
5755
5756
return chart;
5757
}
5758
5759
nv.models.linePlusBarChart = function() {
5760
"use strict";
5761
//============================================================
5762
// Public Variables with Default Settings
5763
//------------------------------------------------------------
5764
5765
var lines = nv.models.line()
5766
, bars = nv.models.historicalBar()
5767
, xAxis = nv.models.axis()
5768
, y1Axis = nv.models.axis()
5769
, y2Axis = nv.models.axis()
5770
, legend = nv.models.legend()
5771
;
5772
5773
var margin = {top: 30, right: 60, bottom: 50, left: 60}
5774
, width = null
5775
, height = null
5776
, getX = function(d) { return d.x }
5777
, getY = function(d) { return d.y }
5778
, color = nv.utils.defaultColor()
5779
, showLegend = true
5780
, tooltips = true
5781
, tooltip = function(key, x, y, e, graph) {
5782
return '<h3>' + key + '</h3>' +
5783
'<p>' + y + ' at ' + x + '</p>';
5784
}
5785
, x
5786
, y1
5787
, y2
5788
, state = {}
5789
, defaultState = null
5790
, noData = "No Data Available."
5791
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
5792
;
5793
5794
bars
5795
.padData(true)
5796
;
5797
lines
5798
.clipEdge(false)
5799
.padData(true)
5800
;
5801
xAxis
5802
.orient('bottom')
5803
.tickPadding(7)
5804
.highlightZero(false)
5805
;
5806
y1Axis
5807
.orient('left')
5808
;
5809
y2Axis
5810
.orient('right')
5811
;
5812
5813
//============================================================
5814
5815
5816
//============================================================
5817
// Private Variables
5818
//------------------------------------------------------------
5819
5820
var showTooltip = function(e, offsetElement) {
5821
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
5822
top = e.pos[1] + ( offsetElement.offsetTop || 0),
5823
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
5824
y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
5825
content = tooltip(e.series.key, x, y, e, chart);
5826
5827
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
5828
}
5829
;
5830
5831
//------------------------------------------------------------
5832
5833
5834
5835
function chart(selection) {
5836
selection.each(function(data) {
5837
var container = d3.select(this),
5838
that = this;
5839
5840
var availableWidth = (width || parseInt(container.style('width')) || 960)
5841
- margin.left - margin.right,
5842
availableHeight = (height || parseInt(container.style('height')) || 400)
5843
- margin.top - margin.bottom;
5844
5845
chart.update = function() { container.transition().call(chart); };
5846
// chart.container = this;
5847
5848
//set state.disabled
5849
state.disabled = data.map(function(d) { return !!d.disabled });
5850
5851
if (!defaultState) {
5852
var key;
5853
defaultState = {};
5854
for (key in state) {
5855
if (state[key] instanceof Array)
5856
defaultState[key] = state[key].slice(0);
5857
else
5858
defaultState[key] = state[key];
5859
}
5860
}
5861
5862
//------------------------------------------------------------
5863
// Display No Data message if there's nothing to show.
5864
5865
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
5866
var noDataText = container.selectAll('.nv-noData').data([noData]);
5867
5868
noDataText.enter().append('text')
5869
.attr('class', 'nvd3 nv-noData')
5870
.attr('dy', '-.7em')
5871
.style('text-anchor', 'middle');
5872
5873
noDataText
5874
.attr('x', margin.left + availableWidth / 2)
5875
.attr('y', margin.top + availableHeight / 2)
5876
.text(function(d) { return d });
5877
5878
return chart;
5879
} else {
5880
container.selectAll('.nv-noData').remove();
5881
}
5882
5883
//------------------------------------------------------------
5884
5885
5886
//------------------------------------------------------------
5887
// Setup Scales
5888
5889
var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
5890
var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
5891
5892
//x = xAxis.scale();
5893
x = dataLines.filter(function(d) { return !d.disabled; }).length && dataLines.filter(function(d) { return !d.disabled; })[0].values.length ? lines.xScale() : bars.xScale();
5894
//x = dataLines.filter(function(d) { return !d.disabled; }).length ? lines.xScale() : bars.xScale(); //old code before change above
5895
y1 = bars.yScale();
5896
y2 = lines.yScale();
5897
5898
//------------------------------------------------------------
5899
5900
//------------------------------------------------------------
5901
// Setup containers and skeleton of chart
5902
5903
var wrap = d3.select(this).selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
5904
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
5905
var g = wrap.select('g');
5906
5907
gEnter.append('g').attr('class', 'nv-x nv-axis');
5908
gEnter.append('g').attr('class', 'nv-y1 nv-axis');
5909
gEnter.append('g').attr('class', 'nv-y2 nv-axis');
5910
gEnter.append('g').attr('class', 'nv-barsWrap');
5911
gEnter.append('g').attr('class', 'nv-linesWrap');
5912
gEnter.append('g').attr('class', 'nv-legendWrap');
5913
5914
//------------------------------------------------------------
5915
5916
5917
//------------------------------------------------------------
5918
// Legend
5919
5920
if (showLegend) {
5921
legend.width( availableWidth / 2 );
5922
5923
g.select('.nv-legendWrap')
5924
.datum(data.map(function(series) {
5925
series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
5926
series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
5927
return series;
5928
}))
5929
.call(legend);
5930
5931
if ( margin.top != legend.height()) {
5932
margin.top = legend.height();
5933
availableHeight = (height || parseInt(container.style('height')) || 400)
5934
- margin.top - margin.bottom;
5935
}
5936
5937
g.select('.nv-legendWrap')
5938
.attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
5939
}
5940
5941
//------------------------------------------------------------
5942
5943
5944
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
5945
5946
5947
//------------------------------------------------------------
5948
// Main Chart Component(s)
5949
5950
5951
lines
5952
.width(availableWidth)
5953
.height(availableHeight)
5954
.color(data.map(function(d,i) {
5955
return d.color || color(d, i);
5956
}).filter(function(d,i) { return !data[i].disabled && !data[i].bar }))
5957
5958
bars
5959
.width(availableWidth)
5960
.height(availableHeight)
5961
.color(data.map(function(d,i) {
5962
return d.color || color(d, i);
5963
}).filter(function(d,i) { return !data[i].disabled && data[i].bar }))
5964
5965
5966
5967
var barsWrap = g.select('.nv-barsWrap')
5968
.datum(dataBars.length ? dataBars : [{values:[]}])
5969
5970
var linesWrap = g.select('.nv-linesWrap')
5971
.datum(dataLines[0] && !dataLines[0].disabled ? dataLines : [{values:[]}] );
5972
//.datum(!dataLines[0].disabled ? dataLines : [{values:dataLines[0].values.map(function(d) { return [d[0], null] }) }] );
5973
5974
d3.transition(barsWrap).call(bars);
5975
d3.transition(linesWrap).call(lines);
5976
5977
//------------------------------------------------------------
5978
5979
5980
//------------------------------------------------------------
5981
// Setup Axes
5982
5983
xAxis
5984
.scale(x)
5985
.ticks( availableWidth / 100 )
5986
.tickSize(-availableHeight, 0);
5987
5988
g.select('.nv-x.nv-axis')
5989
.attr('transform', 'translate(0,' + y1.range()[0] + ')');
5990
d3.transition(g.select('.nv-x.nv-axis'))
5991
.call(xAxis);
5992
5993
5994
y1Axis
5995
.scale(y1)
5996
.ticks( availableHeight / 36 )
5997
.tickSize(-availableWidth, 0);
5998
5999
d3.transition(g.select('.nv-y1.nv-axis'))
6000
.style('opacity', dataBars.length ? 1 : 0)
6001
.call(y1Axis);
6002
6003
6004
y2Axis
6005
.scale(y2)
6006
.ticks( availableHeight / 36 )
6007
.tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
6008
6009
g.select('.nv-y2.nv-axis')
6010
.style('opacity', dataLines.length ? 1 : 0)
6011
.attr('transform', 'translate(' + availableWidth + ',0)');
6012
//.attr('transform', 'translate(' + x.range()[1] + ',0)');
6013
6014
d3.transition(g.select('.nv-y2.nv-axis'))
6015
.call(y2Axis);
6016
6017
//------------------------------------------------------------
6018
6019
6020
//============================================================
6021
// Event Handling/Dispatching (in chart's scope)
6022
//------------------------------------------------------------
6023
6024
legend.dispatch.on('stateChange', function(newState) {
6025
state = newState;
6026
dispatch.stateChange(state);
6027
chart.update();
6028
});
6029
6030
dispatch.on('tooltipShow', function(e) {
6031
if (tooltips) showTooltip(e, that.parentNode);
6032
});
6033
6034
6035
// Update chart from a state object passed to event handler
6036
dispatch.on('changeState', function(e) {
6037
6038
if (typeof e.disabled !== 'undefined') {
6039
data.forEach(function(series,i) {
6040
series.disabled = e.disabled[i];
6041
});
6042
6043
state.disabled = e.disabled;
6044
}
6045
6046
chart.update();
6047
});
6048
6049
//============================================================
6050
6051
6052
});
6053
6054
return chart;
6055
}
6056
6057
6058
//============================================================
6059
// Event Handling/Dispatching (out of chart's scope)
6060
//------------------------------------------------------------
6061
6062
lines.dispatch.on('elementMouseover.tooltip', function(e) {
6063
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6064
dispatch.tooltipShow(e);
6065
});
6066
6067
lines.dispatch.on('elementMouseout.tooltip', function(e) {
6068
dispatch.tooltipHide(e);
6069
});
6070
6071
bars.dispatch.on('elementMouseover.tooltip', function(e) {
6072
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6073
dispatch.tooltipShow(e);
6074
});
6075
6076
bars.dispatch.on('elementMouseout.tooltip', function(e) {
6077
dispatch.tooltipHide(e);
6078
});
6079
6080
dispatch.on('tooltipHide', function() {
6081
if (tooltips) nv.tooltip.cleanup();
6082
});
6083
6084
//============================================================
6085
6086
6087
//============================================================
6088
// Expose Public Variables
6089
//------------------------------------------------------------
6090
6091
// expose chart's sub-components
6092
chart.dispatch = dispatch;
6093
chart.legend = legend;
6094
chart.lines = lines;
6095
chart.bars = bars;
6096
chart.xAxis = xAxis;
6097
chart.y1Axis = y1Axis;
6098
chart.y2Axis = y2Axis;
6099
6100
d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
6101
//TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
6102
//d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6103
6104
chart.options = nv.utils.optionsFunc.bind(chart);
6105
6106
chart.x = function(_) {
6107
if (!arguments.length) return getX;
6108
getX = _;
6109
lines.x(_);
6110
bars.x(_);
6111
return chart;
6112
};
6113
6114
chart.y = function(_) {
6115
if (!arguments.length) return getY;
6116
getY = _;
6117
lines.y(_);
6118
bars.y(_);
6119
return chart;
6120
};
6121
6122
chart.margin = function(_) {
6123
if (!arguments.length) return margin;
6124
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6125
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6126
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6127
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6128
return chart;
6129
};
6130
6131
chart.width = function(_) {
6132
if (!arguments.length) return width;
6133
width = _;
6134
return chart;
6135
};
6136
6137
chart.height = function(_) {
6138
if (!arguments.length) return height;
6139
height = _;
6140
return chart;
6141
};
6142
6143
chart.color = function(_) {
6144
if (!arguments.length) return color;
6145
color = nv.utils.getColor(_);
6146
legend.color(color);
6147
return chart;
6148
};
6149
6150
chart.showLegend = function(_) {
6151
if (!arguments.length) return showLegend;
6152
showLegend = _;
6153
return chart;
6154
};
6155
6156
chart.tooltips = function(_) {
6157
if (!arguments.length) return tooltips;
6158
tooltips = _;
6159
return chart;
6160
};
6161
6162
chart.tooltipContent = function(_) {
6163
if (!arguments.length) return tooltip;
6164
tooltip = _;
6165
return chart;
6166
};
6167
6168
chart.state = function(_) {
6169
if (!arguments.length) return state;
6170
state = _;
6171
return chart;
6172
};
6173
6174
chart.defaultState = function(_) {
6175
if (!arguments.length) return defaultState;
6176
defaultState = _;
6177
return chart;
6178
};
6179
6180
chart.noData = function(_) {
6181
if (!arguments.length) return noData;
6182
noData = _;
6183
return chart;
6184
};
6185
6186
//============================================================
6187
6188
6189
return chart;
6190
}
6191
nv.models.lineWithFocusChart = function() {
6192
"use strict";
6193
//============================================================
6194
// Public Variables with Default Settings
6195
//------------------------------------------------------------
6196
6197
var lines = nv.models.line()
6198
, lines2 = nv.models.line()
6199
, xAxis = nv.models.axis()
6200
, yAxis = nv.models.axis()
6201
, x2Axis = nv.models.axis()
6202
, y2Axis = nv.models.axis()
6203
, legend = nv.models.legend()
6204
, brush = d3.svg.brush()
6205
;
6206
6207
var margin = {top: 30, right: 30, bottom: 30, left: 60}
6208
, margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6209
, color = nv.utils.defaultColor()
6210
, width = null
6211
, height = null
6212
, height2 = 100
6213
, x
6214
, y
6215
, x2
6216
, y2
6217
, showLegend = true
6218
, brushExtent = null
6219
, tooltips = true
6220
, tooltip = function(key, x, y, e, graph) {
6221
return '<h3>' + key + '</h3>' +
6222
'<p>' + y + ' at ' + x + '</p>'
6223
}
6224
, noData = "No Data Available."
6225
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6226
, transitionDuration = 250
6227
;
6228
6229
lines
6230
.clipEdge(true)
6231
;
6232
lines2
6233
.interactive(false)
6234
;
6235
xAxis
6236
.orient('bottom')
6237
.tickPadding(5)
6238
;
6239
yAxis
6240
.orient('left')
6241
;
6242
x2Axis
6243
.orient('bottom')
6244
.tickPadding(5)
6245
;
6246
y2Axis
6247
.orient('left')
6248
;
6249
//============================================================
6250
6251
6252
//============================================================
6253
// Private Variables
6254
//------------------------------------------------------------
6255
6256
var showTooltip = function(e, offsetElement) {
6257
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6258
top = e.pos[1] + ( offsetElement.offsetTop || 0),
6259
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6260
y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex)),
6261
content = tooltip(e.series.key, x, y, e, chart);
6262
6263
nv.tooltip.show([left, top], content, null, null, offsetElement);
6264
};
6265
6266
//============================================================
6267
6268
6269
function chart(selection) {
6270
selection.each(function(data) {
6271
var container = d3.select(this),
6272
that = this;
6273
6274
var availableWidth = (width || parseInt(container.style('width')) || 960)
6275
- margin.left - margin.right,
6276
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6277
- margin.top - margin.bottom - height2,
6278
availableHeight2 = height2 - margin2.top - margin2.bottom;
6279
6280
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
6281
chart.container = this;
6282
6283
6284
//------------------------------------------------------------
6285
// Display No Data message if there's nothing to show.
6286
6287
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6288
var noDataText = container.selectAll('.nv-noData').data([noData]);
6289
6290
noDataText.enter().append('text')
6291
.attr('class', 'nvd3 nv-noData')
6292
.attr('dy', '-.7em')
6293
.style('text-anchor', 'middle');
6294
6295
noDataText
6296
.attr('x', margin.left + availableWidth / 2)
6297
.attr('y', margin.top + availableHeight1 / 2)
6298
.text(function(d) { return d });
6299
6300
return chart;
6301
} else {
6302
container.selectAll('.nv-noData').remove();
6303
}
6304
6305
//------------------------------------------------------------
6306
6307
6308
//------------------------------------------------------------
6309
// Setup Scales
6310
6311
x = lines.xScale();
6312
y = lines.yScale();
6313
x2 = lines2.xScale();
6314
y2 = lines2.yScale();
6315
6316
//------------------------------------------------------------
6317
6318
6319
//------------------------------------------------------------
6320
// Setup containers and skeleton of chart
6321
6322
var wrap = container.selectAll('g.nv-wrap.nv-lineWithFocusChart').data([data]);
6323
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-lineWithFocusChart').append('g');
6324
var g = wrap.select('g');
6325
6326
gEnter.append('g').attr('class', 'nv-legendWrap');
6327
6328
var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6329
focusEnter.append('g').attr('class', 'nv-x nv-axis');
6330
focusEnter.append('g').attr('class', 'nv-y nv-axis');
6331
focusEnter.append('g').attr('class', 'nv-linesWrap');
6332
6333
var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6334
contextEnter.append('g').attr('class', 'nv-x nv-axis');
6335
contextEnter.append('g').attr('class', 'nv-y nv-axis');
6336
contextEnter.append('g').attr('class', 'nv-linesWrap');
6337
contextEnter.append('g').attr('class', 'nv-brushBackground');
6338
contextEnter.append('g').attr('class', 'nv-x nv-brush');
6339
6340
//------------------------------------------------------------
6341
6342
6343
//------------------------------------------------------------
6344
// Legend
6345
6346
if (showLegend) {
6347
legend.width(availableWidth);
6348
6349
g.select('.nv-legendWrap')
6350
.datum(data)
6351
.call(legend);
6352
6353
if ( margin.top != legend.height()) {
6354
margin.top = legend.height();
6355
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6356
- margin.top - margin.bottom - height2;
6357
}
6358
6359
g.select('.nv-legendWrap')
6360
.attr('transform', 'translate(0,' + (-margin.top) +')')
6361
}
6362
6363
//------------------------------------------------------------
6364
6365
6366
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6367
6368
6369
//------------------------------------------------------------
6370
// Main Chart Component(s)
6371
6372
lines
6373
.width(availableWidth)
6374
.height(availableHeight1)
6375
.color(
6376
data
6377
.map(function(d,i) {
6378
return d.color || color(d, i);
6379
})
6380
.filter(function(d,i) {
6381
return !data[i].disabled;
6382
})
6383
);
6384
6385
lines2
6386
.defined(lines.defined())
6387
.width(availableWidth)
6388
.height(availableHeight2)
6389
.color(
6390
data
6391
.map(function(d,i) {
6392
return d.color || color(d, i);
6393
})
6394
.filter(function(d,i) {
6395
return !data[i].disabled;
6396
})
6397
);
6398
6399
g.select('.nv-context')
6400
.attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
6401
6402
var contextLinesWrap = g.select('.nv-context .nv-linesWrap')
6403
.datum(data.filter(function(d) { return !d.disabled }))
6404
6405
d3.transition(contextLinesWrap).call(lines2);
6406
6407
//------------------------------------------------------------
6408
6409
6410
/*
6411
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6412
.datum(data.filter(function(d) { return !d.disabled }))
6413
6414
d3.transition(focusLinesWrap).call(lines);
6415
*/
6416
6417
6418
//------------------------------------------------------------
6419
// Setup Main (Focus) Axes
6420
6421
xAxis
6422
.scale(x)
6423
.ticks( availableWidth / 100 )
6424
.tickSize(-availableHeight1, 0);
6425
6426
yAxis
6427
.scale(y)
6428
.ticks( availableHeight1 / 36 )
6429
.tickSize( -availableWidth, 0);
6430
6431
g.select('.nv-focus .nv-x.nv-axis')
6432
.attr('transform', 'translate(0,' + availableHeight1 + ')');
6433
6434
//------------------------------------------------------------
6435
6436
6437
//------------------------------------------------------------
6438
// Setup Brush
6439
6440
brush
6441
.x(x2)
6442
.on('brush', function() {
6443
//When brushing, turn off transitions because chart needs to change immediately.
6444
var oldTransition = chart.transitionDuration();
6445
chart.transitionDuration(0);
6446
onBrush();
6447
chart.transitionDuration(oldTransition);
6448
});
6449
6450
if (brushExtent) brush.extent(brushExtent);
6451
6452
var brushBG = g.select('.nv-brushBackground').selectAll('g')
6453
.data([brushExtent || brush.extent()])
6454
6455
var brushBGenter = brushBG.enter()
6456
.append('g');
6457
6458
brushBGenter.append('rect')
6459
.attr('class', 'left')
6460
.attr('x', 0)
6461
.attr('y', 0)
6462
.attr('height', availableHeight2);
6463
6464
brushBGenter.append('rect')
6465
.attr('class', 'right')
6466
.attr('x', 0)
6467
.attr('y', 0)
6468
.attr('height', availableHeight2);
6469
6470
var gBrush = g.select('.nv-x.nv-brush')
6471
.call(brush);
6472
gBrush.selectAll('rect')
6473
//.attr('y', -5)
6474
.attr('height', availableHeight2);
6475
gBrush.selectAll('.resize').append('path').attr('d', resizePath);
6476
6477
onBrush();
6478
6479
//------------------------------------------------------------
6480
6481
6482
//------------------------------------------------------------
6483
// Setup Secondary (Context) Axes
6484
6485
x2Axis
6486
.scale(x2)
6487
.ticks( availableWidth / 100 )
6488
.tickSize(-availableHeight2, 0);
6489
6490
g.select('.nv-context .nv-x.nv-axis')
6491
.attr('transform', 'translate(0,' + y2.range()[0] + ')');
6492
d3.transition(g.select('.nv-context .nv-x.nv-axis'))
6493
.call(x2Axis);
6494
6495
6496
y2Axis
6497
.scale(y2)
6498
.ticks( availableHeight2 / 36 )
6499
.tickSize( -availableWidth, 0);
6500
6501
d3.transition(g.select('.nv-context .nv-y.nv-axis'))
6502
.call(y2Axis);
6503
6504
g.select('.nv-context .nv-x.nv-axis')
6505
.attr('transform', 'translate(0,' + y2.range()[0] + ')');
6506
6507
//------------------------------------------------------------
6508
6509
6510
//============================================================
6511
// Event Handling/Dispatching (in chart's scope)
6512
//------------------------------------------------------------
6513
6514
legend.dispatch.on('stateChange', function(newState) {
6515
chart.update();
6516
});
6517
6518
dispatch.on('tooltipShow', function(e) {
6519
if (tooltips) showTooltip(e, that.parentNode);
6520
});
6521
6522
//============================================================
6523
6524
6525
//============================================================
6526
// Functions
6527
//------------------------------------------------------------
6528
6529
// Taken from crossfilter (http://square.github.com/crossfilter/)
6530
function resizePath(d) {
6531
var e = +(d == 'e'),
6532
x = e ? 1 : -1,
6533
y = availableHeight2 / 3;
6534
return 'M' + (.5 * x) + ',' + y
6535
+ 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
6536
+ 'V' + (2 * y - 6)
6537
+ 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
6538
+ 'Z'
6539
+ 'M' + (2.5 * x) + ',' + (y + 8)
6540
+ 'V' + (2 * y - 8)
6541
+ 'M' + (4.5 * x) + ',' + (y + 8)
6542
+ 'V' + (2 * y - 8);
6543
}
6544
6545
6546
function updateBrushBG() {
6547
if (!brush.empty()) brush.extent(brushExtent);
6548
brushBG
6549
.data([brush.empty() ? x2.domain() : brushExtent])
6550
.each(function(d,i) {
6551
var leftWidth = x2(d[0]) - x.range()[0],
6552
rightWidth = x.range()[1] - x2(d[1]);
6553
d3.select(this).select('.left')
6554
.attr('width', leftWidth < 0 ? 0 : leftWidth);
6555
6556
d3.select(this).select('.right')
6557
.attr('x', x2(d[1]))
6558
.attr('width', rightWidth < 0 ? 0 : rightWidth);
6559
});
6560
}
6561
6562
6563
function onBrush() {
6564
brushExtent = brush.empty() ? null : brush.extent();
6565
var extent = brush.empty() ? x2.domain() : brush.extent();
6566
6567
//The brush extent cannot be less than one. If it is, don't update the line chart.
6568
if (Math.abs(extent[0] - extent[1]) <= 1) {
6569
return;
6570
}
6571
6572
dispatch.brush({extent: extent, brush: brush});
6573
6574
6575
updateBrushBG();
6576
6577
// Update Main (Focus)
6578
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
6579
.datum(
6580
data
6581
.filter(function(d) { return !d.disabled })
6582
.map(function(d,i) {
6583
return {
6584
key: d.key,
6585
values: d.values.filter(function(d,i) {
6586
return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
6587
})
6588
}
6589
})
6590
);
6591
focusLinesWrap.transition().duration(transitionDuration).call(lines);
6592
6593
6594
// Update Main (Focus) Axes
6595
g.select('.nv-focus .nv-x.nv-axis').transition().duration(transitionDuration)
6596
.call(xAxis);
6597
g.select('.nv-focus .nv-y.nv-axis').transition().duration(transitionDuration)
6598
.call(yAxis);
6599
}
6600
6601
//============================================================
6602
6603
6604
});
6605
6606
return chart;
6607
}
6608
6609
6610
//============================================================
6611
// Event Handling/Dispatching (out of chart's scope)
6612
//------------------------------------------------------------
6613
6614
lines.dispatch.on('elementMouseover.tooltip', function(e) {
6615
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
6616
dispatch.tooltipShow(e);
6617
});
6618
6619
lines.dispatch.on('elementMouseout.tooltip', function(e) {
6620
dispatch.tooltipHide(e);
6621
});
6622
6623
dispatch.on('tooltipHide', function() {
6624
if (tooltips) nv.tooltip.cleanup();
6625
});
6626
6627
//============================================================
6628
6629
6630
//============================================================
6631
// Expose Public Variables
6632
//------------------------------------------------------------
6633
6634
// expose chart's sub-components
6635
chart.dispatch = dispatch;
6636
chart.legend = legend;
6637
chart.lines = lines;
6638
chart.lines2 = lines2;
6639
chart.xAxis = xAxis;
6640
chart.yAxis = yAxis;
6641
chart.x2Axis = x2Axis;
6642
chart.y2Axis = y2Axis;
6643
6644
d3.rebind(chart, lines, 'defined', 'isArea', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
6645
6646
chart.options = nv.utils.optionsFunc.bind(chart);
6647
6648
chart.x = function(_) {
6649
if (!arguments.length) return lines.x;
6650
lines.x(_);
6651
lines2.x(_);
6652
return chart;
6653
};
6654
6655
chart.y = function(_) {
6656
if (!arguments.length) return lines.y;
6657
lines.y(_);
6658
lines2.y(_);
6659
return chart;
6660
};
6661
6662
chart.margin = function(_) {
6663
if (!arguments.length) return margin;
6664
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
6665
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
6666
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
6667
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
6668
return chart;
6669
};
6670
6671
chart.margin2 = function(_) {
6672
if (!arguments.length) return margin2;
6673
margin2 = _;
6674
return chart;
6675
};
6676
6677
chart.width = function(_) {
6678
if (!arguments.length) return width;
6679
width = _;
6680
return chart;
6681
};
6682
6683
chart.height = function(_) {
6684
if (!arguments.length) return height;
6685
height = _;
6686
return chart;
6687
};
6688
6689
chart.height2 = function(_) {
6690
if (!arguments.length) return height2;
6691
height2 = _;
6692
return chart;
6693
};
6694
6695
chart.color = function(_) {
6696
if (!arguments.length) return color;
6697
color =nv.utils.getColor(_);
6698
legend.color(color);
6699
return chart;
6700
};
6701
6702
chart.showLegend = function(_) {
6703
if (!arguments.length) return showLegend;
6704
showLegend = _;
6705
return chart;
6706
};
6707
6708
chart.tooltips = function(_) {
6709
if (!arguments.length) return tooltips;
6710
tooltips = _;
6711
return chart;
6712
};
6713
6714
chart.tooltipContent = function(_) {
6715
if (!arguments.length) return tooltip;
6716
tooltip = _;
6717
return chart;
6718
};
6719
6720
chart.interpolate = function(_) {
6721
if (!arguments.length) return lines.interpolate();
6722
lines.interpolate(_);
6723
lines2.interpolate(_);
6724
return chart;
6725
};
6726
6727
chart.noData = function(_) {
6728
if (!arguments.length) return noData;
6729
noData = _;
6730
return chart;
6731
};
6732
6733
// Chart has multiple similar Axes, to prevent code duplication, probably need to link all axis functions manually like below
6734
chart.xTickFormat = function(_) {
6735
if (!arguments.length) return xAxis.tickFormat();
6736
xAxis.tickFormat(_);
6737
x2Axis.tickFormat(_);
6738
return chart;
6739
};
6740
6741
chart.yTickFormat = function(_) {
6742
if (!arguments.length) return yAxis.tickFormat();
6743
yAxis.tickFormat(_);
6744
y2Axis.tickFormat(_);
6745
return chart;
6746
};
6747
6748
chart.brushExtent = function(_) {
6749
if (!arguments.length) return brushExtent;
6750
brushExtent = _;
6751
return chart;
6752
};
6753
6754
chart.transitionDuration = function(_) {
6755
if (!arguments.length) return transitionDuration;
6756
transitionDuration = _;
6757
return chart;
6758
};
6759
6760
//============================================================
6761
6762
6763
return chart;
6764
}
6765
6766
nv.models.linePlusBarWithFocusChart = function() {
6767
"use strict";
6768
//============================================================
6769
// Public Variables with Default Settings
6770
//------------------------------------------------------------
6771
6772
var lines = nv.models.line()
6773
, lines2 = nv.models.line()
6774
, bars = nv.models.historicalBar()
6775
, bars2 = nv.models.historicalBar()
6776
, xAxis = nv.models.axis()
6777
, x2Axis = nv.models.axis()
6778
, y1Axis = nv.models.axis()
6779
, y2Axis = nv.models.axis()
6780
, y3Axis = nv.models.axis()
6781
, y4Axis = nv.models.axis()
6782
, legend = nv.models.legend()
6783
, brush = d3.svg.brush()
6784
;
6785
6786
var margin = {top: 30, right: 30, bottom: 30, left: 60}
6787
, margin2 = {top: 0, right: 30, bottom: 20, left: 60}
6788
, width = null
6789
, height = null
6790
, height2 = 100
6791
, getX = function(d) { return d.x }
6792
, getY = function(d) { return d.y }
6793
, color = nv.utils.defaultColor()
6794
, showLegend = true
6795
, extent
6796
, brushExtent = null
6797
, tooltips = true
6798
, tooltip = function(key, x, y, e, graph) {
6799
return '<h3>' + key + '</h3>' +
6800
'<p>' + y + ' at ' + x + '</p>';
6801
}
6802
, x
6803
, x2
6804
, y1
6805
, y2
6806
, y3
6807
, y4
6808
, noData = "No Data Available."
6809
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'brush')
6810
, transitionDuration = 0
6811
;
6812
6813
lines
6814
.clipEdge(true)
6815
;
6816
lines2
6817
.interactive(false)
6818
;
6819
xAxis
6820
.orient('bottom')
6821
.tickPadding(5)
6822
;
6823
y1Axis
6824
.orient('left')
6825
;
6826
y2Axis
6827
.orient('right')
6828
;
6829
x2Axis
6830
.orient('bottom')
6831
.tickPadding(5)
6832
;
6833
y3Axis
6834
.orient('left')
6835
;
6836
y4Axis
6837
.orient('right')
6838
;
6839
6840
//============================================================
6841
6842
6843
//============================================================
6844
// Private Variables
6845
//------------------------------------------------------------
6846
6847
var showTooltip = function(e, offsetElement) {
6848
if (extent) {
6849
e.pointIndex += Math.ceil(extent[0]);
6850
}
6851
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
6852
top = e.pos[1] + ( offsetElement.offsetTop || 0),
6853
x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
6854
y = (e.series.bar ? y1Axis : y2Axis).tickFormat()(lines.y()(e.point, e.pointIndex)),
6855
content = tooltip(e.series.key, x, y, e, chart);
6856
6857
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
6858
};
6859
6860
//------------------------------------------------------------
6861
6862
6863
6864
function chart(selection) {
6865
selection.each(function(data) {
6866
var container = d3.select(this),
6867
that = this;
6868
6869
var availableWidth = (width || parseInt(container.style('width')) || 960)
6870
- margin.left - margin.right,
6871
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6872
- margin.top - margin.bottom - height2,
6873
availableHeight2 = height2 - margin2.top - margin2.bottom;
6874
6875
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
6876
chart.container = this;
6877
6878
6879
//------------------------------------------------------------
6880
// Display No Data message if there's nothing to show.
6881
6882
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
6883
var noDataText = container.selectAll('.nv-noData').data([noData]);
6884
6885
noDataText.enter().append('text')
6886
.attr('class', 'nvd3 nv-noData')
6887
.attr('dy', '-.7em')
6888
.style('text-anchor', 'middle');
6889
6890
noDataText
6891
.attr('x', margin.left + availableWidth / 2)
6892
.attr('y', margin.top + availableHeight1 / 2)
6893
.text(function(d) { return d });
6894
6895
return chart;
6896
} else {
6897
container.selectAll('.nv-noData').remove();
6898
}
6899
6900
//------------------------------------------------------------
6901
6902
6903
//------------------------------------------------------------
6904
// Setup Scales
6905
6906
var dataBars = data.filter(function(d) { return !d.disabled && d.bar });
6907
var dataLines = data.filter(function(d) { return !d.bar }); // removed the !d.disabled clause here to fix Issue #240
6908
6909
x = bars.xScale();
6910
x2 = x2Axis.scale();
6911
y1 = bars.yScale();
6912
y2 = lines.yScale();
6913
y3 = bars2.yScale();
6914
y4 = lines2.yScale();
6915
6916
var series1 = data
6917
.filter(function(d) { return !d.disabled && d.bar })
6918
.map(function(d) {
6919
return d.values.map(function(d,i) {
6920
return { x: getX(d,i), y: getY(d,i) }
6921
})
6922
});
6923
6924
var series2 = data
6925
.filter(function(d) { return !d.disabled && !d.bar })
6926
.map(function(d) {
6927
return d.values.map(function(d,i) {
6928
return { x: getX(d,i), y: getY(d,i) }
6929
})
6930
});
6931
6932
x .range([0, availableWidth]);
6933
6934
x2 .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
6935
.range([0, availableWidth]);
6936
6937
6938
//------------------------------------------------------------
6939
6940
6941
//------------------------------------------------------------
6942
// Setup containers and skeleton of chart
6943
6944
var wrap = container.selectAll('g.nv-wrap.nv-linePlusBar').data([data]);
6945
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-linePlusBar').append('g');
6946
var g = wrap.select('g');
6947
6948
gEnter.append('g').attr('class', 'nv-legendWrap');
6949
6950
var focusEnter = gEnter.append('g').attr('class', 'nv-focus');
6951
focusEnter.append('g').attr('class', 'nv-x nv-axis');
6952
focusEnter.append('g').attr('class', 'nv-y1 nv-axis');
6953
focusEnter.append('g').attr('class', 'nv-y2 nv-axis');
6954
focusEnter.append('g').attr('class', 'nv-barsWrap');
6955
focusEnter.append('g').attr('class', 'nv-linesWrap');
6956
6957
var contextEnter = gEnter.append('g').attr('class', 'nv-context');
6958
contextEnter.append('g').attr('class', 'nv-x nv-axis');
6959
contextEnter.append('g').attr('class', 'nv-y1 nv-axis');
6960
contextEnter.append('g').attr('class', 'nv-y2 nv-axis');
6961
contextEnter.append('g').attr('class', 'nv-barsWrap');
6962
contextEnter.append('g').attr('class', 'nv-linesWrap');
6963
contextEnter.append('g').attr('class', 'nv-brushBackground');
6964
contextEnter.append('g').attr('class', 'nv-x nv-brush');
6965
6966
6967
//------------------------------------------------------------
6968
6969
6970
//------------------------------------------------------------
6971
// Legend
6972
6973
if (showLegend) {
6974
legend.width( availableWidth / 2 );
6975
6976
g.select('.nv-legendWrap')
6977
.datum(data.map(function(series) {
6978
series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
6979
series.key = series.originalKey + (series.bar ? ' (left axis)' : ' (right axis)');
6980
return series;
6981
}))
6982
.call(legend);
6983
6984
if ( margin.top != legend.height()) {
6985
margin.top = legend.height();
6986
availableHeight1 = (height || parseInt(container.style('height')) || 400)
6987
- margin.top - margin.bottom - height2;
6988
}
6989
6990
g.select('.nv-legendWrap')
6991
.attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
6992
}
6993
6994
//------------------------------------------------------------
6995
6996
6997
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
6998
6999
7000
//------------------------------------------------------------
7001
// Context Components
7002
7003
bars2
7004
.width(availableWidth)
7005
.height(availableHeight2)
7006
.color(data.map(function(d,i) {
7007
return d.color || color(d, i);
7008
}).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7009
7010
lines2
7011
.width(availableWidth)
7012
.height(availableHeight2)
7013
.color(data.map(function(d,i) {
7014
return d.color || color(d, i);
7015
}).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7016
7017
var bars2Wrap = g.select('.nv-context .nv-barsWrap')
7018
.datum(dataBars.length ? dataBars : [{values:[]}]);
7019
7020
var lines2Wrap = g.select('.nv-context .nv-linesWrap')
7021
.datum(!dataLines[0].disabled ? dataLines : [{values:[]}]);
7022
7023
g.select('.nv-context')
7024
.attr('transform', 'translate(0,' + ( availableHeight1 + margin.bottom + margin2.top) + ')')
7025
7026
bars2Wrap.transition().call(bars2);
7027
lines2Wrap.transition().call(lines2);
7028
7029
//------------------------------------------------------------
7030
7031
7032
7033
//------------------------------------------------------------
7034
// Setup Brush
7035
7036
brush
7037
.x(x2)
7038
.on('brush', onBrush);
7039
7040
if (brushExtent) brush.extent(brushExtent);
7041
7042
var brushBG = g.select('.nv-brushBackground').selectAll('g')
7043
.data([brushExtent || brush.extent()])
7044
7045
var brushBGenter = brushBG.enter()
7046
.append('g');
7047
7048
brushBGenter.append('rect')
7049
.attr('class', 'left')
7050
.attr('x', 0)
7051
.attr('y', 0)
7052
.attr('height', availableHeight2);
7053
7054
brushBGenter.append('rect')
7055
.attr('class', 'right')
7056
.attr('x', 0)
7057
.attr('y', 0)
7058
.attr('height', availableHeight2);
7059
7060
var gBrush = g.select('.nv-x.nv-brush')
7061
.call(brush);
7062
gBrush.selectAll('rect')
7063
//.attr('y', -5)
7064
.attr('height', availableHeight2);
7065
gBrush.selectAll('.resize').append('path').attr('d', resizePath);
7066
7067
//------------------------------------------------------------
7068
7069
//------------------------------------------------------------
7070
// Setup Secondary (Context) Axes
7071
7072
x2Axis
7073
.ticks( availableWidth / 100 )
7074
.tickSize(-availableHeight2, 0);
7075
7076
g.select('.nv-context .nv-x.nv-axis')
7077
.attr('transform', 'translate(0,' + y3.range()[0] + ')');
7078
g.select('.nv-context .nv-x.nv-axis').transition()
7079
.call(x2Axis);
7080
7081
7082
y3Axis
7083
.scale(y3)
7084
.ticks( availableHeight2 / 36 )
7085
.tickSize( -availableWidth, 0);
7086
7087
g.select('.nv-context .nv-y1.nv-axis')
7088
.style('opacity', dataBars.length ? 1 : 0)
7089
.attr('transform', 'translate(0,' + x2.range()[0] + ')');
7090
7091
g.select('.nv-context .nv-y1.nv-axis').transition()
7092
.call(y3Axis);
7093
7094
7095
y4Axis
7096
.scale(y4)
7097
.ticks( availableHeight2 / 36 )
7098
.tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7099
7100
g.select('.nv-context .nv-y2.nv-axis')
7101
.style('opacity', dataLines.length ? 1 : 0)
7102
.attr('transform', 'translate(' + x2.range()[1] + ',0)');
7103
7104
g.select('.nv-context .nv-y2.nv-axis').transition()
7105
.call(y4Axis);
7106
7107
//------------------------------------------------------------
7108
7109
//============================================================
7110
// Event Handling/Dispatching (in chart's scope)
7111
//------------------------------------------------------------
7112
7113
legend.dispatch.on('stateChange', function(newState) {
7114
chart.update();
7115
});
7116
7117
dispatch.on('tooltipShow', function(e) {
7118
if (tooltips) showTooltip(e, that.parentNode);
7119
});
7120
7121
//============================================================
7122
7123
7124
//============================================================
7125
// Functions
7126
//------------------------------------------------------------
7127
7128
// Taken from crossfilter (http://square.github.com/crossfilter/)
7129
function resizePath(d) {
7130
var e = +(d == 'e'),
7131
x = e ? 1 : -1,
7132
y = availableHeight2 / 3;
7133
return 'M' + (.5 * x) + ',' + y
7134
+ 'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6)
7135
+ 'V' + (2 * y - 6)
7136
+ 'A6,6 0 0 ' + e + ' ' + (.5 * x) + ',' + (2 * y)
7137
+ 'Z'
7138
+ 'M' + (2.5 * x) + ',' + (y + 8)
7139
+ 'V' + (2 * y - 8)
7140
+ 'M' + (4.5 * x) + ',' + (y + 8)
7141
+ 'V' + (2 * y - 8);
7142
}
7143
7144
7145
function updateBrushBG() {
7146
if (!brush.empty()) brush.extent(brushExtent);
7147
brushBG
7148
.data([brush.empty() ? x2.domain() : brushExtent])
7149
.each(function(d,i) {
7150
var leftWidth = x2(d[0]) - x2.range()[0],
7151
rightWidth = x2.range()[1] - x2(d[1]);
7152
d3.select(this).select('.left')
7153
.attr('width', leftWidth < 0 ? 0 : leftWidth);
7154
7155
d3.select(this).select('.right')
7156
.attr('x', x2(d[1]))
7157
.attr('width', rightWidth < 0 ? 0 : rightWidth);
7158
});
7159
}
7160
7161
7162
function onBrush() {
7163
brushExtent = brush.empty() ? null : brush.extent();
7164
extent = brush.empty() ? x2.domain() : brush.extent();
7165
7166
7167
dispatch.brush({extent: extent, brush: brush});
7168
7169
updateBrushBG();
7170
7171
7172
//------------------------------------------------------------
7173
// Prepare Main (Focus) Bars and Lines
7174
7175
bars
7176
.width(availableWidth)
7177
.height(availableHeight1)
7178
.color(data.map(function(d,i) {
7179
return d.color || color(d, i);
7180
}).filter(function(d,i) { return !data[i].disabled && data[i].bar }));
7181
7182
7183
lines
7184
.width(availableWidth)
7185
.height(availableHeight1)
7186
.color(data.map(function(d,i) {
7187
return d.color || color(d, i);
7188
}).filter(function(d,i) { return !data[i].disabled && !data[i].bar }));
7189
7190
var focusBarsWrap = g.select('.nv-focus .nv-barsWrap')
7191
.datum(!dataBars.length ? [{values:[]}] :
7192
dataBars
7193
.map(function(d,i) {
7194
return {
7195
key: d.key,
7196
values: d.values.filter(function(d,i) {
7197
return bars.x()(d,i) >= extent[0] && bars.x()(d,i) <= extent[1];
7198
})
7199
}
7200
})
7201
);
7202
7203
var focusLinesWrap = g.select('.nv-focus .nv-linesWrap')
7204
.datum(dataLines[0].disabled ? [{values:[]}] :
7205
dataLines
7206
.map(function(d,i) {
7207
return {
7208
key: d.key,
7209
values: d.values.filter(function(d,i) {
7210
return lines.x()(d,i) >= extent[0] && lines.x()(d,i) <= extent[1];
7211
})
7212
}
7213
})
7214
);
7215
7216
//------------------------------------------------------------
7217
7218
7219
//------------------------------------------------------------
7220
// Update Main (Focus) X Axis
7221
7222
if (dataBars.length) {
7223
x = bars.xScale();
7224
} else {
7225
x = lines.xScale();
7226
}
7227
7228
xAxis
7229
.scale(x)
7230
.ticks( availableWidth / 100 )
7231
.tickSize(-availableHeight1, 0);
7232
7233
xAxis.domain([Math.ceil(extent[0]), Math.floor(extent[1])]);
7234
7235
g.select('.nv-x.nv-axis').transition().duration(transitionDuration)
7236
.call(xAxis);
7237
//------------------------------------------------------------
7238
7239
7240
//------------------------------------------------------------
7241
// Update Main (Focus) Bars and Lines
7242
7243
focusBarsWrap.transition().duration(transitionDuration).call(bars);
7244
focusLinesWrap.transition().duration(transitionDuration).call(lines);
7245
7246
//------------------------------------------------------------
7247
7248
7249
//------------------------------------------------------------
7250
// Setup and Update Main (Focus) Y Axes
7251
7252
g.select('.nv-focus .nv-x.nv-axis')
7253
.attr('transform', 'translate(0,' + y1.range()[0] + ')');
7254
7255
7256
y1Axis
7257
.scale(y1)
7258
.ticks( availableHeight1 / 36 )
7259
.tickSize(-availableWidth, 0);
7260
7261
g.select('.nv-focus .nv-y1.nv-axis')
7262
.style('opacity', dataBars.length ? 1 : 0);
7263
7264
7265
y2Axis
7266
.scale(y2)
7267
.ticks( availableHeight1 / 36 )
7268
.tickSize(dataBars.length ? 0 : -availableWidth, 0); // Show the y2 rules only if y1 has none
7269
7270
g.select('.nv-focus .nv-y2.nv-axis')
7271
.style('opacity', dataLines.length ? 1 : 0)
7272
.attr('transform', 'translate(' + x.range()[1] + ',0)');
7273
7274
g.select('.nv-focus .nv-y1.nv-axis').transition().duration(transitionDuration)
7275
.call(y1Axis);
7276
g.select('.nv-focus .nv-y2.nv-axis').transition().duration(transitionDuration)
7277
.call(y2Axis);
7278
}
7279
7280
//============================================================
7281
7282
onBrush();
7283
7284
});
7285
7286
return chart;
7287
}
7288
7289
7290
//============================================================
7291
// Event Handling/Dispatching (out of chart's scope)
7292
//------------------------------------------------------------
7293
7294
lines.dispatch.on('elementMouseover.tooltip', function(e) {
7295
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7296
dispatch.tooltipShow(e);
7297
});
7298
7299
lines.dispatch.on('elementMouseout.tooltip', function(e) {
7300
dispatch.tooltipHide(e);
7301
});
7302
7303
bars.dispatch.on('elementMouseover.tooltip', function(e) {
7304
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
7305
dispatch.tooltipShow(e);
7306
});
7307
7308
bars.dispatch.on('elementMouseout.tooltip', function(e) {
7309
dispatch.tooltipHide(e);
7310
});
7311
7312
dispatch.on('tooltipHide', function() {
7313
if (tooltips) nv.tooltip.cleanup();
7314
});
7315
7316
//============================================================
7317
7318
7319
//============================================================
7320
// Expose Public Variables
7321
//------------------------------------------------------------
7322
7323
// expose chart's sub-components
7324
chart.dispatch = dispatch;
7325
chart.legend = legend;
7326
chart.lines = lines;
7327
chart.lines2 = lines2;
7328
chart.bars = bars;
7329
chart.bars2 = bars2;
7330
chart.xAxis = xAxis;
7331
chart.x2Axis = x2Axis;
7332
chart.y1Axis = y1Axis;
7333
chart.y2Axis = y2Axis;
7334
chart.y3Axis = y3Axis;
7335
chart.y4Axis = y4Axis;
7336
7337
d3.rebind(chart, lines, 'defined', 'size', 'clipVoronoi', 'interpolate');
7338
//TODO: consider rebinding x, y and some other stuff, and simply do soemthign lile bars.x(lines.x()), etc.
7339
//d3.rebind(chart, lines, 'x', 'y', 'size', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'interactive', 'clipEdge', 'clipVoronoi', 'id');
7340
7341
chart.options = nv.utils.optionsFunc.bind(chart);
7342
7343
chart.x = function(_) {
7344
if (!arguments.length) return getX;
7345
getX = _;
7346
lines.x(_);
7347
bars.x(_);
7348
return chart;
7349
};
7350
7351
chart.y = function(_) {
7352
if (!arguments.length) return getY;
7353
getY = _;
7354
lines.y(_);
7355
bars.y(_);
7356
return chart;
7357
};
7358
7359
chart.margin = function(_) {
7360
if (!arguments.length) return margin;
7361
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7362
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7363
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7364
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7365
return chart;
7366
};
7367
7368
chart.width = function(_) {
7369
if (!arguments.length) return width;
7370
width = _;
7371
return chart;
7372
};
7373
7374
chart.height = function(_) {
7375
if (!arguments.length) return height;
7376
height = _;
7377
return chart;
7378
};
7379
7380
chart.color = function(_) {
7381
if (!arguments.length) return color;
7382
color = nv.utils.getColor(_);
7383
legend.color(color);
7384
return chart;
7385
};
7386
7387
chart.showLegend = function(_) {
7388
if (!arguments.length) return showLegend;
7389
showLegend = _;
7390
return chart;
7391
};
7392
7393
chart.tooltips = function(_) {
7394
if (!arguments.length) return tooltips;
7395
tooltips = _;
7396
return chart;
7397
};
7398
7399
chart.tooltipContent = function(_) {
7400
if (!arguments.length) return tooltip;
7401
tooltip = _;
7402
return chart;
7403
};
7404
7405
chart.noData = function(_) {
7406
if (!arguments.length) return noData;
7407
noData = _;
7408
return chart;
7409
};
7410
7411
chart.brushExtent = function(_) {
7412
if (!arguments.length) return brushExtent;
7413
brushExtent = _;
7414
return chart;
7415
};
7416
7417
7418
//============================================================
7419
7420
7421
return chart;
7422
}
7423
7424
nv.models.multiBar = function() {
7425
"use strict";
7426
//============================================================
7427
// Public Variables with Default Settings
7428
//------------------------------------------------------------
7429
7430
var margin = {top: 0, right: 0, bottom: 0, left: 0}
7431
, width = 960
7432
, height = 500
7433
, x = d3.scale.ordinal()
7434
, y = d3.scale.linear()
7435
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
7436
, getX = function(d) { return d.x }
7437
, getY = function(d) { return d.y }
7438
, forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
7439
, clipEdge = true
7440
, stacked = false
7441
, color = nv.utils.defaultColor()
7442
, hideable = false
7443
, barColor = null // adding the ability to set the color for each rather than the whole group
7444
, disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
7445
, delay = 1200
7446
, xDomain
7447
, yDomain
7448
, xRange
7449
, yRange
7450
, groupSpacing = 0.1
7451
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
7452
;
7453
7454
//============================================================
7455
7456
7457
//============================================================
7458
// Private Variables
7459
//------------------------------------------------------------
7460
7461
var x0, y0 //used to store previous scales
7462
;
7463
7464
//============================================================
7465
7466
7467
function chart(selection) {
7468
selection.each(function(data) {
7469
var availableWidth = width - margin.left - margin.right,
7470
availableHeight = height - margin.top - margin.bottom,
7471
container = d3.select(this);
7472
7473
if(hideable && data.length) hideable = [{
7474
values: data[0].values.map(function(d) {
7475
return {
7476
x: d.x,
7477
y: 0,
7478
series: d.series,
7479
size: 0.01
7480
};}
7481
)}];
7482
7483
if (stacked)
7484
data = d3.layout.stack()
7485
.offset('zero')
7486
.values(function(d){ return d.values })
7487
.y(getY)
7488
(!data.length && hideable ? hideable : data);
7489
7490
7491
//add series index to each data point for reference
7492
data = data.map(function(series, i) {
7493
series.values = series.values.map(function(point) {
7494
point.series = i;
7495
return point;
7496
});
7497
return series;
7498
});
7499
7500
7501
//------------------------------------------------------------
7502
// HACK for negative value stacking
7503
if (stacked)
7504
data[0].values.map(function(d,i) {
7505
var posBase = 0, negBase = 0;
7506
data.map(function(d) {
7507
var f = d.values[i]
7508
f.size = Math.abs(f.y);
7509
if (f.y<0) {
7510
f.y1 = negBase;
7511
negBase = negBase - f.size;
7512
} else
7513
{
7514
f.y1 = f.size + posBase;
7515
posBase = posBase + f.size;
7516
}
7517
});
7518
});
7519
7520
//------------------------------------------------------------
7521
// Setup Scales
7522
7523
// remap and flatten the data for use in calculating the scales' domains
7524
var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
7525
data.map(function(d) {
7526
return d.values.map(function(d,i) {
7527
return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
7528
})
7529
});
7530
7531
x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
7532
.rangeBands(xRange || [0, availableWidth], groupSpacing);
7533
7534
//y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y1 : 0) }).concat(forceY)))
7535
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)))
7536
.range(yRange || [availableHeight, 0]);
7537
7538
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
7539
if (x.domain()[0] === x.domain()[1])
7540
x.domain()[0] ?
7541
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
7542
: x.domain([-1,1]);
7543
7544
if (y.domain()[0] === y.domain()[1])
7545
y.domain()[0] ?
7546
y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
7547
: y.domain([-1,1]);
7548
7549
7550
x0 = x0 || x;
7551
y0 = y0 || y;
7552
7553
//------------------------------------------------------------
7554
7555
7556
//------------------------------------------------------------
7557
// Setup containers and skeleton of chart
7558
7559
var wrap = container.selectAll('g.nv-wrap.nv-multibar').data([data]);
7560
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibar');
7561
var defsEnter = wrapEnter.append('defs');
7562
var gEnter = wrapEnter.append('g');
7563
var g = wrap.select('g')
7564
7565
gEnter.append('g').attr('class', 'nv-groups');
7566
7567
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
7568
7569
//------------------------------------------------------------
7570
7571
7572
7573
defsEnter.append('clipPath')
7574
.attr('id', 'nv-edge-clip-' + id)
7575
.append('rect');
7576
wrap.select('#nv-edge-clip-' + id + ' rect')
7577
.attr('width', availableWidth)
7578
.attr('height', availableHeight);
7579
7580
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
7581
7582
7583
7584
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
7585
.data(function(d) { return d }, function(d,i) { return i });
7586
groups.enter().append('g')
7587
.style('stroke-opacity', 1e-6)
7588
.style('fill-opacity', 1e-6);
7589
groups.exit()
7590
.transition()
7591
.selectAll('rect.nv-bar')
7592
.delay(function(d,i) {
7593
return i * delay/ data[0].values.length;
7594
})
7595
.attr('y', function(d) { return stacked ? y0(d.y0) : y0(0) })
7596
.attr('height', 0)
7597
.remove();
7598
groups
7599
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
7600
.classed('hover', function(d) { return d.hover })
7601
.style('fill', function(d,i){ return color(d, i) })
7602
.style('stroke', function(d,i){ return color(d, i) });
7603
groups
7604
.transition()
7605
.style('stroke-opacity', 1)
7606
.style('fill-opacity', .75);
7607
7608
7609
var bars = groups.selectAll('rect.nv-bar')
7610
.data(function(d) { return (hideable && !data.length) ? hideable.values : d.values });
7611
7612
bars.exit().remove();
7613
7614
7615
var barsEnter = bars.enter().append('rect')
7616
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7617
.attr('x', function(d,i,j) {
7618
return stacked ? 0 : (j * x.rangeBand() / data.length )
7619
})
7620
.attr('y', function(d) { return y0(stacked ? d.y0 : 0) })
7621
.attr('height', 0)
7622
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) )
7623
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7624
;
7625
bars
7626
.style('fill', function(d,i,j){ return color(d, j, i); })
7627
.style('stroke', function(d,i,j){ return color(d, j, i); })
7628
.on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
7629
d3.select(this).classed('hover', true);
7630
dispatch.elementMouseover({
7631
value: getY(d,i),
7632
point: d,
7633
series: data[d.series],
7634
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
7635
pointIndex: i,
7636
seriesIndex: d.series,
7637
e: d3.event
7638
});
7639
})
7640
.on('mouseout', function(d,i) {
7641
d3.select(this).classed('hover', false);
7642
dispatch.elementMouseout({
7643
value: getY(d,i),
7644
point: d,
7645
series: data[d.series],
7646
pointIndex: i,
7647
seriesIndex: d.series,
7648
e: d3.event
7649
});
7650
})
7651
.on('click', function(d,i) {
7652
dispatch.elementClick({
7653
value: getY(d,i),
7654
point: d,
7655
series: data[d.series],
7656
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
7657
pointIndex: i,
7658
seriesIndex: d.series,
7659
e: d3.event
7660
});
7661
d3.event.stopPropagation();
7662
})
7663
.on('dblclick', function(d,i) {
7664
dispatch.elementDblClick({
7665
value: getY(d,i),
7666
point: d,
7667
series: data[d.series],
7668
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
7669
pointIndex: i,
7670
seriesIndex: d.series,
7671
e: d3.event
7672
});
7673
d3.event.stopPropagation();
7674
});
7675
bars
7676
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
7677
.transition()
7678
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',0)'; })
7679
7680
if (barColor) {
7681
if (!disabled) disabled = data.map(function() { return true });
7682
bars
7683
.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(); })
7684
.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(); });
7685
}
7686
7687
7688
if (stacked)
7689
bars.transition()
7690
.delay(function(d,i) {
7691
7692
return i * delay / data[0].values.length;
7693
})
7694
.attr('y', function(d,i) {
7695
7696
return y((stacked ? d.y1 : 0));
7697
})
7698
.attr('height', function(d,i) {
7699
return Math.max(Math.abs(y(d.y + (stacked ? d.y0 : 0)) - y((stacked ? d.y0 : 0))),1);
7700
})
7701
.attr('x', function(d,i) {
7702
return stacked ? 0 : (d.series * x.rangeBand() / data.length )
7703
})
7704
.attr('width', x.rangeBand() / (stacked ? 1 : data.length) );
7705
else
7706
bars.transition()
7707
.delay(function(d,i) {
7708
return i * delay/ data[0].values.length;
7709
})
7710
.attr('x', function(d,i) {
7711
return d.series * x.rangeBand() / data.length
7712
})
7713
.attr('width', x.rangeBand() / data.length)
7714
.attr('y', function(d,i) {
7715
return getY(d,i) < 0 ?
7716
y(0) :
7717
y(0) - y(getY(d,i)) < 1 ?
7718
y(0) - 1 :
7719
y(getY(d,i)) || 0;
7720
})
7721
.attr('height', function(d,i) {
7722
return Math.max(Math.abs(y(getY(d,i)) - y(0)),1) || 0;
7723
});
7724
7725
7726
7727
//store old scales for use in transitions on update
7728
x0 = x.copy();
7729
y0 = y.copy();
7730
7731
});
7732
7733
return chart;
7734
}
7735
7736
7737
//============================================================
7738
// Expose Public Variables
7739
//------------------------------------------------------------
7740
7741
chart.dispatch = dispatch;
7742
7743
chart.options = nv.utils.optionsFunc.bind(chart);
7744
7745
chart.x = function(_) {
7746
if (!arguments.length) return getX;
7747
getX = _;
7748
return chart;
7749
};
7750
7751
chart.y = function(_) {
7752
if (!arguments.length) return getY;
7753
getY = _;
7754
return chart;
7755
};
7756
7757
chart.margin = function(_) {
7758
if (!arguments.length) return margin;
7759
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
7760
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
7761
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
7762
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
7763
return chart;
7764
};
7765
7766
chart.width = function(_) {
7767
if (!arguments.length) return width;
7768
width = _;
7769
return chart;
7770
};
7771
7772
chart.height = function(_) {
7773
if (!arguments.length) return height;
7774
height = _;
7775
return chart;
7776
};
7777
7778
chart.xScale = function(_) {
7779
if (!arguments.length) return x;
7780
x = _;
7781
return chart;
7782
};
7783
7784
chart.yScale = function(_) {
7785
if (!arguments.length) return y;
7786
y = _;
7787
return chart;
7788
};
7789
7790
chart.xDomain = function(_) {
7791
if (!arguments.length) return xDomain;
7792
xDomain = _;
7793
return chart;
7794
};
7795
7796
chart.yDomain = function(_) {
7797
if (!arguments.length) return yDomain;
7798
yDomain = _;
7799
return chart;
7800
};
7801
7802
chart.xRange = function(_) {
7803
if (!arguments.length) return xRange;
7804
xRange = _;
7805
return chart;
7806
};
7807
7808
chart.yRange = function(_) {
7809
if (!arguments.length) return yRange;
7810
yRange = _;
7811
return chart;
7812
};
7813
7814
chart.forceY = function(_) {
7815
if (!arguments.length) return forceY;
7816
forceY = _;
7817
return chart;
7818
};
7819
7820
chart.stacked = function(_) {
7821
if (!arguments.length) return stacked;
7822
stacked = _;
7823
return chart;
7824
};
7825
7826
chart.clipEdge = function(_) {
7827
if (!arguments.length) return clipEdge;
7828
clipEdge = _;
7829
return chart;
7830
};
7831
7832
chart.color = function(_) {
7833
if (!arguments.length) return color;
7834
color = nv.utils.getColor(_);
7835
return chart;
7836
};
7837
7838
chart.barColor = function(_) {
7839
if (!arguments.length) return barColor;
7840
barColor = nv.utils.getColor(_);
7841
return chart;
7842
};
7843
7844
chart.disabled = function(_) {
7845
if (!arguments.length) return disabled;
7846
disabled = _;
7847
return chart;
7848
};
7849
7850
chart.id = function(_) {
7851
if (!arguments.length) return id;
7852
id = _;
7853
return chart;
7854
};
7855
7856
chart.hideable = function(_) {
7857
if (!arguments.length) return hideable;
7858
hideable = _;
7859
return chart;
7860
};
7861
7862
chart.delay = function(_) {
7863
if (!arguments.length) return delay;
7864
delay = _;
7865
return chart;
7866
};
7867
7868
chart.groupSpacing = function(_) {
7869
if (!arguments.length) return groupSpacing;
7870
groupSpacing = _;
7871
return chart;
7872
};
7873
7874
//============================================================
7875
7876
7877
return chart;
7878
}
7879
7880
nv.models.multiBarChart = function() {
7881
"use strict";
7882
//============================================================
7883
// Public Variables with Default Settings
7884
//------------------------------------------------------------
7885
7886
var multibar = nv.models.multiBar()
7887
, xAxis = nv.models.axis()
7888
, yAxis = nv.models.axis()
7889
, legend = nv.models.legend()
7890
, controls = nv.models.legend()
7891
;
7892
7893
var margin = {top: 30, right: 20, bottom: 50, left: 60}
7894
, width = null
7895
, height = null
7896
, color = nv.utils.defaultColor()
7897
, showControls = true
7898
, showLegend = true
7899
, showXAxis = true
7900
, showYAxis = true
7901
, rightAlignYAxis = false
7902
, reduceXTicks = true // if false a tick will show for every data point
7903
, staggerLabels = false
7904
, rotateLabels = 0
7905
, tooltips = true
7906
, tooltip = function(key, x, y, e, graph) {
7907
return '<h3>' + key + '</h3>' +
7908
'<p>' + y + ' on ' + x + '</p>'
7909
}
7910
, x //can be accessed via chart.xScale()
7911
, y //can be accessed via chart.yScale()
7912
, state = { stacked: false }
7913
, defaultState = null
7914
, noData = "No Data Available."
7915
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
7916
, controlWidth = function() { return showControls ? 180 : 0 }
7917
, transitionDuration = 250
7918
;
7919
7920
multibar
7921
.stacked(false)
7922
;
7923
xAxis
7924
.orient('bottom')
7925
.tickPadding(7)
7926
.highlightZero(true)
7927
.showMaxMin(false)
7928
.tickFormat(function(d) { return d })
7929
;
7930
yAxis
7931
.orient((rightAlignYAxis) ? 'right' : 'left')
7932
.tickFormat(d3.format(',.1f'))
7933
;
7934
7935
controls.updateState(false);
7936
//============================================================
7937
7938
7939
//============================================================
7940
// Private Variables
7941
//------------------------------------------------------------
7942
7943
var showTooltip = function(e, offsetElement) {
7944
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
7945
top = e.pos[1] + ( offsetElement.offsetTop || 0),
7946
x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
7947
y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
7948
content = tooltip(e.series.key, x, y, e, chart);
7949
7950
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
7951
};
7952
7953
//============================================================
7954
7955
7956
function chart(selection) {
7957
selection.each(function(data) {
7958
var container = d3.select(this),
7959
that = this;
7960
7961
var availableWidth = (width || parseInt(container.style('width')) || 960)
7962
- margin.left - margin.right,
7963
availableHeight = (height || parseInt(container.style('height')) || 400)
7964
- margin.top - margin.bottom;
7965
7966
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
7967
chart.container = this;
7968
7969
//set state.disabled
7970
state.disabled = data.map(function(d) { return !!d.disabled });
7971
7972
if (!defaultState) {
7973
var key;
7974
defaultState = {};
7975
for (key in state) {
7976
if (state[key] instanceof Array)
7977
defaultState[key] = state[key].slice(0);
7978
else
7979
defaultState[key] = state[key];
7980
}
7981
}
7982
//------------------------------------------------------------
7983
// Display noData message if there's nothing to show.
7984
7985
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
7986
var noDataText = container.selectAll('.nv-noData').data([noData]);
7987
7988
noDataText.enter().append('text')
7989
.attr('class', 'nvd3 nv-noData')
7990
.attr('dy', '-.7em')
7991
.style('text-anchor', 'middle');
7992
7993
noDataText
7994
.attr('x', margin.left + availableWidth / 2)
7995
.attr('y', margin.top + availableHeight / 2)
7996
.text(function(d) { return d });
7997
7998
return chart;
7999
} else {
8000
container.selectAll('.nv-noData').remove();
8001
}
8002
8003
//------------------------------------------------------------
8004
8005
8006
//------------------------------------------------------------
8007
// Setup Scales
8008
8009
x = multibar.xScale();
8010
y = multibar.yScale();
8011
8012
//------------------------------------------------------------
8013
8014
8015
//------------------------------------------------------------
8016
// Setup containers and skeleton of chart
8017
8018
var wrap = container.selectAll('g.nv-wrap.nv-multiBarWithLegend').data([data]);
8019
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarWithLegend').append('g');
8020
var g = wrap.select('g');
8021
8022
gEnter.append('g').attr('class', 'nv-x nv-axis');
8023
gEnter.append('g').attr('class', 'nv-y nv-axis');
8024
gEnter.append('g').attr('class', 'nv-barsWrap');
8025
gEnter.append('g').attr('class', 'nv-legendWrap');
8026
gEnter.append('g').attr('class', 'nv-controlsWrap');
8027
8028
//------------------------------------------------------------
8029
8030
8031
//------------------------------------------------------------
8032
// Legend
8033
8034
if (showLegend) {
8035
legend.width(availableWidth - controlWidth());
8036
8037
if (multibar.barColor())
8038
data.forEach(function(series,i) {
8039
series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8040
})
8041
8042
g.select('.nv-legendWrap')
8043
.datum(data)
8044
.call(legend);
8045
8046
if ( margin.top != legend.height()) {
8047
margin.top = legend.height();
8048
availableHeight = (height || parseInt(container.style('height')) || 400)
8049
- margin.top - margin.bottom;
8050
}
8051
8052
g.select('.nv-legendWrap')
8053
.attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
8054
}
8055
8056
//------------------------------------------------------------
8057
8058
8059
//------------------------------------------------------------
8060
// Controls
8061
8062
if (showControls) {
8063
var controlsData = [
8064
{ key: 'Grouped', disabled: multibar.stacked() },
8065
{ key: 'Stacked', disabled: !multibar.stacked() }
8066
];
8067
8068
controls.width(controlWidth()).color(['#444', '#444', '#444']);
8069
g.select('.nv-controlsWrap')
8070
.datum(controlsData)
8071
.attr('transform', 'translate(0,' + (-margin.top) +')')
8072
.call(controls);
8073
}
8074
8075
//------------------------------------------------------------
8076
8077
8078
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8079
8080
if (rightAlignYAxis) {
8081
g.select(".nv-y.nv-axis")
8082
.attr("transform", "translate(" + availableWidth + ",0)");
8083
}
8084
8085
//------------------------------------------------------------
8086
// Main Chart Component(s)
8087
8088
multibar
8089
.disabled(data.map(function(series) { return series.disabled }))
8090
.width(availableWidth)
8091
.height(availableHeight)
8092
.color(data.map(function(d,i) {
8093
return d.color || color(d, i);
8094
}).filter(function(d,i) { return !data[i].disabled }))
8095
8096
8097
var barsWrap = g.select('.nv-barsWrap')
8098
.datum(data.filter(function(d) { return !d.disabled }))
8099
8100
barsWrap.transition().call(multibar);
8101
8102
//------------------------------------------------------------
8103
8104
8105
//------------------------------------------------------------
8106
// Setup Axes
8107
8108
if (showXAxis) {
8109
xAxis
8110
.scale(x)
8111
.ticks( availableWidth / 100 )
8112
.tickSize(-availableHeight, 0);
8113
8114
g.select('.nv-x.nv-axis')
8115
.attr('transform', 'translate(0,' + y.range()[0] + ')');
8116
g.select('.nv-x.nv-axis').transition()
8117
.call(xAxis);
8118
8119
var xTicks = g.select('.nv-x.nv-axis > g').selectAll('g');
8120
8121
xTicks
8122
.selectAll('line, text')
8123
.style('opacity', 1)
8124
8125
if (staggerLabels) {
8126
var getTranslate = function(x,y) {
8127
return "translate(" + x + "," + y + ")";
8128
};
8129
8130
var staggerUp = 5, staggerDown = 17; //pixels to stagger by
8131
// Issue #140
8132
xTicks
8133
.selectAll("text")
8134
.attr('transform', function(d,i,j) {
8135
return getTranslate(0, (j % 2 == 0 ? staggerUp : staggerDown));
8136
});
8137
8138
var totalInBetweenTicks = d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;
8139
g.selectAll(".nv-x.nv-axis .nv-axisMaxMin text")
8140
.attr("transform", function(d,i) {
8141
return getTranslate(0, (i === 0 || totalInBetweenTicks % 2 !== 0) ? staggerDown : staggerUp);
8142
});
8143
}
8144
8145
if (reduceXTicks)
8146
xTicks
8147
.filter(function(d,i) {
8148
return i % Math.ceil(data[0].values.length / (availableWidth / 100)) !== 0;
8149
})
8150
.selectAll('text, line')
8151
.style('opacity', 0);
8152
8153
if(rotateLabels)
8154
xTicks
8155
.selectAll('.tick text')
8156
.attr('transform', 'rotate(' + rotateLabels + ' 0,0)')
8157
.style('text-anchor', rotateLabels > 0 ? 'start' : 'end');
8158
8159
g.select('.nv-x.nv-axis').selectAll('g.nv-axisMaxMin text')
8160
.style('opacity', 1);
8161
}
8162
8163
8164
if (showYAxis) {
8165
yAxis
8166
.scale(y)
8167
.ticks( availableHeight / 36 )
8168
.tickSize( -availableWidth, 0);
8169
8170
g.select('.nv-y.nv-axis').transition()
8171
.call(yAxis);
8172
}
8173
8174
8175
//------------------------------------------------------------
8176
8177
8178
8179
//============================================================
8180
// Event Handling/Dispatching (in chart's scope)
8181
//------------------------------------------------------------
8182
8183
legend.dispatch.on('stateChange', function(newState) {
8184
state = newState;
8185
dispatch.stateChange(state);
8186
chart.update();
8187
});
8188
8189
controls.dispatch.on('legendClick', function(d,i) {
8190
if (!d.disabled) return;
8191
controlsData = controlsData.map(function(s) {
8192
s.disabled = true;
8193
return s;
8194
});
8195
d.disabled = false;
8196
8197
switch (d.key) {
8198
case 'Grouped':
8199
multibar.stacked(false);
8200
break;
8201
case 'Stacked':
8202
multibar.stacked(true);
8203
break;
8204
}
8205
8206
state.stacked = multibar.stacked();
8207
dispatch.stateChange(state);
8208
8209
chart.update();
8210
});
8211
8212
dispatch.on('tooltipShow', function(e) {
8213
if (tooltips) showTooltip(e, that.parentNode)
8214
});
8215
8216
// Update chart from a state object passed to event handler
8217
dispatch.on('changeState', function(e) {
8218
8219
if (typeof e.disabled !== 'undefined') {
8220
data.forEach(function(series,i) {
8221
series.disabled = e.disabled[i];
8222
});
8223
8224
state.disabled = e.disabled;
8225
}
8226
8227
if (typeof e.stacked !== 'undefined') {
8228
multibar.stacked(e.stacked);
8229
state.stacked = e.stacked;
8230
}
8231
8232
chart.update();
8233
});
8234
8235
//============================================================
8236
8237
8238
});
8239
8240
return chart;
8241
}
8242
8243
8244
//============================================================
8245
// Event Handling/Dispatching (out of chart's scope)
8246
//------------------------------------------------------------
8247
8248
multibar.dispatch.on('elementMouseover.tooltip', function(e) {
8249
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
8250
dispatch.tooltipShow(e);
8251
});
8252
8253
multibar.dispatch.on('elementMouseout.tooltip', function(e) {
8254
dispatch.tooltipHide(e);
8255
});
8256
dispatch.on('tooltipHide', function() {
8257
if (tooltips) nv.tooltip.cleanup();
8258
});
8259
8260
//============================================================
8261
8262
8263
//============================================================
8264
// Expose Public Variables
8265
//------------------------------------------------------------
8266
8267
// expose chart's sub-components
8268
chart.dispatch = dispatch;
8269
chart.multibar = multibar;
8270
chart.legend = legend;
8271
chart.xAxis = xAxis;
8272
chart.yAxis = yAxis;
8273
8274
d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge',
8275
'id', 'stacked', 'delay', 'barColor','groupSpacing');
8276
8277
chart.options = nv.utils.optionsFunc.bind(chart);
8278
8279
chart.margin = function(_) {
8280
if (!arguments.length) return margin;
8281
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8282
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8283
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8284
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8285
return chart;
8286
};
8287
8288
chart.width = function(_) {
8289
if (!arguments.length) return width;
8290
width = _;
8291
return chart;
8292
};
8293
8294
chart.height = function(_) {
8295
if (!arguments.length) return height;
8296
height = _;
8297
return chart;
8298
};
8299
8300
chart.color = function(_) {
8301
if (!arguments.length) return color;
8302
color = nv.utils.getColor(_);
8303
legend.color(color);
8304
return chart;
8305
};
8306
8307
chart.showControls = function(_) {
8308
if (!arguments.length) return showControls;
8309
showControls = _;
8310
return chart;
8311
};
8312
8313
chart.showLegend = function(_) {
8314
if (!arguments.length) return showLegend;
8315
showLegend = _;
8316
return chart;
8317
};
8318
8319
chart.showXAxis = function(_) {
8320
if (!arguments.length) return showXAxis;
8321
showXAxis = _;
8322
return chart;
8323
};
8324
8325
chart.showYAxis = function(_) {
8326
if (!arguments.length) return showYAxis;
8327
showYAxis = _;
8328
return chart;
8329
};
8330
8331
chart.rightAlignYAxis = function(_) {
8332
if(!arguments.length) return rightAlignYAxis;
8333
rightAlignYAxis = _;
8334
yAxis.orient( (_) ? 'right' : 'left');
8335
return chart;
8336
};
8337
8338
chart.reduceXTicks= function(_) {
8339
if (!arguments.length) return reduceXTicks;
8340
reduceXTicks = _;
8341
return chart;
8342
};
8343
8344
chart.rotateLabels = function(_) {
8345
if (!arguments.length) return rotateLabels;
8346
rotateLabels = _;
8347
return chart;
8348
}
8349
8350
chart.staggerLabels = function(_) {
8351
if (!arguments.length) return staggerLabels;
8352
staggerLabels = _;
8353
return chart;
8354
};
8355
8356
chart.tooltip = function(_) {
8357
if (!arguments.length) return tooltip;
8358
tooltip = _;
8359
return chart;
8360
};
8361
8362
chart.tooltips = function(_) {
8363
if (!arguments.length) return tooltips;
8364
tooltips = _;
8365
return chart;
8366
};
8367
8368
chart.tooltipContent = function(_) {
8369
if (!arguments.length) return tooltip;
8370
tooltip = _;
8371
return chart;
8372
};
8373
8374
chart.state = function(_) {
8375
if (!arguments.length) return state;
8376
state = _;
8377
return chart;
8378
};
8379
8380
chart.defaultState = function(_) {
8381
if (!arguments.length) return defaultState;
8382
defaultState = _;
8383
return chart;
8384
};
8385
8386
chart.noData = function(_) {
8387
if (!arguments.length) return noData;
8388
noData = _;
8389
return chart;
8390
};
8391
8392
chart.transitionDuration = function(_) {
8393
if (!arguments.length) return transitionDuration;
8394
transitionDuration = _;
8395
return chart;
8396
};
8397
8398
//============================================================
8399
8400
8401
return chart;
8402
}
8403
8404
nv.models.multiBarHorizontal = function() {
8405
"use strict";
8406
//============================================================
8407
// Public Variables with Default Settings
8408
//------------------------------------------------------------
8409
8410
var margin = {top: 0, right: 0, bottom: 0, left: 0}
8411
, width = 960
8412
, height = 500
8413
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
8414
, x = d3.scale.ordinal()
8415
, y = d3.scale.linear()
8416
, getX = function(d) { return d.x }
8417
, getY = function(d) { return d.y }
8418
, forceY = [0] // 0 is forced by default.. this makes sense for the majority of bar graphs... user can always do chart.forceY([]) to remove
8419
, color = nv.utils.defaultColor()
8420
, barColor = null // adding the ability to set the color for each rather than the whole group
8421
, disabled // used in conjunction with barColor to communicate from multiBarHorizontalChart what series are disabled
8422
, stacked = false
8423
, showValues = false
8424
, valuePadding = 60
8425
, valueFormat = d3.format(',.2f')
8426
, delay = 1200
8427
, xDomain
8428
, yDomain
8429
, xRange
8430
, yRange
8431
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
8432
;
8433
8434
//============================================================
8435
8436
8437
//============================================================
8438
// Private Variables
8439
//------------------------------------------------------------
8440
8441
var x0, y0 //used to store previous scales
8442
;
8443
8444
//============================================================
8445
8446
8447
function chart(selection) {
8448
selection.each(function(data) {
8449
var availableWidth = width - margin.left - margin.right,
8450
availableHeight = height - margin.top - margin.bottom,
8451
container = d3.select(this);
8452
8453
8454
if (stacked)
8455
data = d3.layout.stack()
8456
.offset('zero')
8457
.values(function(d){ return d.values })
8458
.y(getY)
8459
(data);
8460
8461
8462
//add series index to each data point for reference
8463
data = data.map(function(series, i) {
8464
series.values = series.values.map(function(point) {
8465
point.series = i;
8466
return point;
8467
});
8468
return series;
8469
});
8470
8471
8472
8473
//------------------------------------------------------------
8474
// HACK for negative value stacking
8475
if (stacked)
8476
data[0].values.map(function(d,i) {
8477
var posBase = 0, negBase = 0;
8478
data.map(function(d) {
8479
var f = d.values[i]
8480
f.size = Math.abs(f.y);
8481
if (f.y<0) {
8482
f.y1 = negBase - f.size;
8483
negBase = negBase - f.size;
8484
} else
8485
{
8486
f.y1 = posBase;
8487
posBase = posBase + f.size;
8488
}
8489
});
8490
});
8491
8492
8493
8494
//------------------------------------------------------------
8495
// Setup Scales
8496
8497
// remap and flatten the data for use in calculating the scales' domains
8498
var seriesData = (xDomain && yDomain) ? [] : // if we know xDomain and yDomain, no need to calculate
8499
data.map(function(d) {
8500
return d.values.map(function(d,i) {
8501
return { x: getX(d,i), y: getY(d,i), y0: d.y0, y1: d.y1 }
8502
})
8503
});
8504
8505
x .domain(xDomain || d3.merge(seriesData).map(function(d) { return d.x }))
8506
.rangeBands(xRange || [0, availableHeight], .1);
8507
8508
//y .domain(yDomain || d3.extent(d3.merge(seriesData).map(function(d) { return d.y + (stacked ? d.y0 : 0) }).concat(forceY)))
8509
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)))
8510
8511
if (showValues && !stacked)
8512
y.range(yRange || [(y.domain()[0] < 0 ? valuePadding : 0), availableWidth - (y.domain()[1] > 0 ? valuePadding : 0) ]);
8513
else
8514
y.range(yRange || [0, availableWidth]);
8515
8516
x0 = x0 || x;
8517
y0 = y0 || d3.scale.linear().domain(y.domain()).range([y(0),y(0)]);
8518
8519
//------------------------------------------------------------
8520
8521
8522
//------------------------------------------------------------
8523
// Setup containers and skeleton of chart
8524
8525
var wrap = d3.select(this).selectAll('g.nv-wrap.nv-multibarHorizontal').data([data]);
8526
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multibarHorizontal');
8527
var defsEnter = wrapEnter.append('defs');
8528
var gEnter = wrapEnter.append('g');
8529
var g = wrap.select('g');
8530
8531
gEnter.append('g').attr('class', 'nv-groups');
8532
8533
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
8534
8535
//------------------------------------------------------------
8536
8537
8538
8539
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
8540
.data(function(d) { return d }, function(d,i) { return i });
8541
groups.enter().append('g')
8542
.style('stroke-opacity', 1e-6)
8543
.style('fill-opacity', 1e-6);
8544
groups.exit().transition()
8545
.style('stroke-opacity', 1e-6)
8546
.style('fill-opacity', 1e-6)
8547
.remove();
8548
groups
8549
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
8550
.classed('hover', function(d) { return d.hover })
8551
.style('fill', function(d,i){ return color(d, i) })
8552
.style('stroke', function(d,i){ return color(d, i) });
8553
groups.transition()
8554
.style('stroke-opacity', 1)
8555
.style('fill-opacity', .75);
8556
8557
8558
var bars = groups.selectAll('g.nv-bar')
8559
.data(function(d) { return d.values });
8560
8561
bars.exit().remove();
8562
8563
8564
var barsEnter = bars.enter().append('g')
8565
.attr('transform', function(d,i,j) {
8566
return 'translate(' + y0(stacked ? d.y0 : 0) + ',' + (stacked ? 0 : (j * x.rangeBand() / data.length ) + x(getX(d,i))) + ')'
8567
});
8568
8569
barsEnter.append('rect')
8570
.attr('width', 0)
8571
.attr('height', x.rangeBand() / (stacked ? 1 : data.length) )
8572
8573
bars
8574
.on('mouseover', function(d,i) { //TODO: figure out why j works above, but not here
8575
d3.select(this).classed('hover', true);
8576
dispatch.elementMouseover({
8577
value: getY(d,i),
8578
point: d,
8579
series: data[d.series],
8580
pos: [ y(getY(d,i) + (stacked ? d.y0 : 0)), x(getX(d,i)) + (x.rangeBand() * (stacked ? data.length / 2 : d.series + .5) / data.length) ],
8581
pointIndex: i,
8582
seriesIndex: d.series,
8583
e: d3.event
8584
});
8585
})
8586
.on('mouseout', function(d,i) {
8587
d3.select(this).classed('hover', false);
8588
dispatch.elementMouseout({
8589
value: getY(d,i),
8590
point: d,
8591
series: data[d.series],
8592
pointIndex: i,
8593
seriesIndex: d.series,
8594
e: d3.event
8595
});
8596
})
8597
.on('click', function(d,i) {
8598
dispatch.elementClick({
8599
value: getY(d,i),
8600
point: d,
8601
series: data[d.series],
8602
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
8603
pointIndex: i,
8604
seriesIndex: d.series,
8605
e: d3.event
8606
});
8607
d3.event.stopPropagation();
8608
})
8609
.on('dblclick', function(d,i) {
8610
dispatch.elementDblClick({
8611
value: getY(d,i),
8612
point: d,
8613
series: data[d.series],
8614
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
8615
pointIndex: i,
8616
seriesIndex: d.series,
8617
e: d3.event
8618
});
8619
d3.event.stopPropagation();
8620
});
8621
8622
8623
barsEnter.append('text');
8624
8625
if (showValues && !stacked) {
8626
bars.select('text')
8627
.attr('text-anchor', function(d,i) { return getY(d,i) < 0 ? 'end' : 'start' })
8628
.attr('y', x.rangeBand() / (data.length * 2))
8629
.attr('dy', '.32em')
8630
.text(function(d,i) { return valueFormat(getY(d,i)) })
8631
bars.transition()
8632
.select('text')
8633
.attr('x', function(d,i) { return getY(d,i) < 0 ? -4 : y(getY(d,i)) - y(0) + 4 })
8634
} else {
8635
bars.selectAll('text').text('');
8636
}
8637
8638
bars
8639
.attr('class', function(d,i) { return getY(d,i) < 0 ? 'nv-bar negative' : 'nv-bar positive'})
8640
8641
if (barColor) {
8642
if (!disabled) disabled = data.map(function() { return true });
8643
bars
8644
.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(); })
8645
.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(); });
8646
}
8647
8648
if (stacked)
8649
bars.transition()
8650
.attr('transform', function(d,i) {
8651
return 'translate(' + y(d.y1) + ',' + x(getX(d,i)) + ')'
8652
})
8653
.select('rect')
8654
.attr('width', function(d,i) {
8655
return Math.abs(y(getY(d,i) + d.y0) - y(d.y0))
8656
})
8657
.attr('height', x.rangeBand() );
8658
else
8659
bars.transition()
8660
.attr('transform', function(d,i) {
8661
//TODO: stacked must be all positive or all negative, not both?
8662
return 'translate(' +
8663
(getY(d,i) < 0 ? y(getY(d,i)) : y(0))
8664
+ ',' +
8665
(d.series * x.rangeBand() / data.length
8666
+
8667
x(getX(d,i)) )
8668
+ ')'
8669
})
8670
.select('rect')
8671
.attr('height', x.rangeBand() / data.length )
8672
.attr('width', function(d,i) {
8673
return Math.max(Math.abs(y(getY(d,i)) - y(0)),1)
8674
});
8675
8676
8677
//store old scales for use in transitions on update
8678
x0 = x.copy();
8679
y0 = y.copy();
8680
8681
});
8682
8683
return chart;
8684
}
8685
8686
8687
//============================================================
8688
// Expose Public Variables
8689
//------------------------------------------------------------
8690
8691
chart.dispatch = dispatch;
8692
8693
chart.options = nv.utils.optionsFunc.bind(chart);
8694
8695
chart.x = function(_) {
8696
if (!arguments.length) return getX;
8697
getX = _;
8698
return chart;
8699
};
8700
8701
chart.y = function(_) {
8702
if (!arguments.length) return getY;
8703
getY = _;
8704
return chart;
8705
};
8706
8707
chart.margin = function(_) {
8708
if (!arguments.length) return margin;
8709
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
8710
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
8711
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
8712
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
8713
return chart;
8714
};
8715
8716
chart.width = function(_) {
8717
if (!arguments.length) return width;
8718
width = _;
8719
return chart;
8720
};
8721
8722
chart.height = function(_) {
8723
if (!arguments.length) return height;
8724
height = _;
8725
return chart;
8726
};
8727
8728
chart.xScale = function(_) {
8729
if (!arguments.length) return x;
8730
x = _;
8731
return chart;
8732
};
8733
8734
chart.yScale = function(_) {
8735
if (!arguments.length) return y;
8736
y = _;
8737
return chart;
8738
};
8739
8740
chart.xDomain = function(_) {
8741
if (!arguments.length) return xDomain;
8742
xDomain = _;
8743
return chart;
8744
};
8745
8746
chart.yDomain = function(_) {
8747
if (!arguments.length) return yDomain;
8748
yDomain = _;
8749
return chart;
8750
};
8751
8752
chart.xRange = function(_) {
8753
if (!arguments.length) return xRange;
8754
xRange = _;
8755
return chart;
8756
};
8757
8758
chart.yRange = function(_) {
8759
if (!arguments.length) return yRange;
8760
yRange = _;
8761
return chart;
8762
};
8763
8764
chart.forceY = function(_) {
8765
if (!arguments.length) return forceY;
8766
forceY = _;
8767
return chart;
8768
};
8769
8770
chart.stacked = function(_) {
8771
if (!arguments.length) return stacked;
8772
stacked = _;
8773
return chart;
8774
};
8775
8776
chart.color = function(_) {
8777
if (!arguments.length) return color;
8778
color = nv.utils.getColor(_);
8779
return chart;
8780
};
8781
8782
chart.barColor = function(_) {
8783
if (!arguments.length) return barColor;
8784
barColor = nv.utils.getColor(_);
8785
return chart;
8786
};
8787
8788
chart.disabled = function(_) {
8789
if (!arguments.length) return disabled;
8790
disabled = _;
8791
return chart;
8792
};
8793
8794
chart.id = function(_) {
8795
if (!arguments.length) return id;
8796
id = _;
8797
return chart;
8798
};
8799
8800
chart.delay = function(_) {
8801
if (!arguments.length) return delay;
8802
delay = _;
8803
return chart;
8804
};
8805
8806
chart.showValues = function(_) {
8807
if (!arguments.length) return showValues;
8808
showValues = _;
8809
return chart;
8810
};
8811
8812
chart.valueFormat= function(_) {
8813
if (!arguments.length) return valueFormat;
8814
valueFormat = _;
8815
return chart;
8816
};
8817
8818
chart.valuePadding = function(_) {
8819
if (!arguments.length) return valuePadding;
8820
valuePadding = _;
8821
return chart;
8822
};
8823
8824
//============================================================
8825
8826
8827
return chart;
8828
}
8829
8830
nv.models.multiBarHorizontalChart = function() {
8831
"use strict";
8832
//============================================================
8833
// Public Variables with Default Settings
8834
//------------------------------------------------------------
8835
8836
var multibar = nv.models.multiBarHorizontal()
8837
, xAxis = nv.models.axis()
8838
, yAxis = nv.models.axis()
8839
, legend = nv.models.legend().height(30)
8840
, controls = nv.models.legend().height(30)
8841
;
8842
8843
var margin = {top: 30, right: 20, bottom: 50, left: 60}
8844
, width = null
8845
, height = null
8846
, color = nv.utils.defaultColor()
8847
, showControls = true
8848
, showLegend = true
8849
, stacked = false
8850
, tooltips = true
8851
, tooltip = function(key, x, y, e, graph) {
8852
return '<h3>' + key + ' - ' + x + '</h3>' +
8853
'<p>' + y + '</p>'
8854
}
8855
, x //can be accessed via chart.xScale()
8856
, y //can be accessed via chart.yScale()
8857
, state = { stacked: stacked }
8858
, defaultState = null
8859
, noData = 'No Data Available.'
8860
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
8861
, controlWidth = function() { return showControls ? 180 : 0 }
8862
, transitionDuration = 250
8863
;
8864
8865
multibar
8866
.stacked(stacked)
8867
;
8868
xAxis
8869
.orient('left')
8870
.tickPadding(5)
8871
.highlightZero(false)
8872
.showMaxMin(false)
8873
.tickFormat(function(d) { return d })
8874
;
8875
yAxis
8876
.orient('bottom')
8877
.tickFormat(d3.format(',.1f'))
8878
;
8879
8880
controls.updateState(false);
8881
//============================================================
8882
8883
8884
//============================================================
8885
// Private Variables
8886
//------------------------------------------------------------
8887
8888
var showTooltip = function(e, offsetElement) {
8889
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
8890
top = e.pos[1] + ( offsetElement.offsetTop || 0),
8891
x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
8892
y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex)),
8893
content = tooltip(e.series.key, x, y, e, chart);
8894
8895
nv.tooltip.show([left, top], content, e.value < 0 ? 'e' : 'w', null, offsetElement);
8896
};
8897
8898
//============================================================
8899
8900
8901
function chart(selection) {
8902
selection.each(function(data) {
8903
var container = d3.select(this),
8904
that = this;
8905
8906
var availableWidth = (width || parseInt(container.style('width')) || 960)
8907
- margin.left - margin.right,
8908
availableHeight = (height || parseInt(container.style('height')) || 400)
8909
- margin.top - margin.bottom;
8910
8911
chart.update = function() { container.transition().duration(transitionDuration).call(chart) };
8912
chart.container = this;
8913
8914
//set state.disabled
8915
state.disabled = data.map(function(d) { return !!d.disabled });
8916
8917
if (!defaultState) {
8918
var key;
8919
defaultState = {};
8920
for (key in state) {
8921
if (state[key] instanceof Array)
8922
defaultState[key] = state[key].slice(0);
8923
else
8924
defaultState[key] = state[key];
8925
}
8926
}
8927
8928
//------------------------------------------------------------
8929
// Display No Data message if there's nothing to show.
8930
8931
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
8932
var noDataText = container.selectAll('.nv-noData').data([noData]);
8933
8934
noDataText.enter().append('text')
8935
.attr('class', 'nvd3 nv-noData')
8936
.attr('dy', '-.7em')
8937
.style('text-anchor', 'middle');
8938
8939
noDataText
8940
.attr('x', margin.left + availableWidth / 2)
8941
.attr('y', margin.top + availableHeight / 2)
8942
.text(function(d) { return d });
8943
8944
return chart;
8945
} else {
8946
container.selectAll('.nv-noData').remove();
8947
}
8948
8949
//------------------------------------------------------------
8950
8951
8952
//------------------------------------------------------------
8953
// Setup Scales
8954
8955
x = multibar.xScale();
8956
y = multibar.yScale();
8957
8958
//------------------------------------------------------------
8959
8960
8961
//------------------------------------------------------------
8962
// Setup containers and skeleton of chart
8963
8964
var wrap = container.selectAll('g.nv-wrap.nv-multiBarHorizontalChart').data([data]);
8965
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-multiBarHorizontalChart').append('g');
8966
var g = wrap.select('g');
8967
8968
gEnter.append('g').attr('class', 'nv-x nv-axis');
8969
gEnter.append('g').attr('class', 'nv-y nv-axis');
8970
gEnter.append('g').attr('class', 'nv-barsWrap');
8971
gEnter.append('g').attr('class', 'nv-legendWrap');
8972
gEnter.append('g').attr('class', 'nv-controlsWrap');
8973
8974
//------------------------------------------------------------
8975
8976
8977
//------------------------------------------------------------
8978
// Legend
8979
8980
if (showLegend) {
8981
legend.width(availableWidth - controlWidth());
8982
8983
if (multibar.barColor())
8984
data.forEach(function(series,i) {
8985
series.color = d3.rgb('#ccc').darker(i * 1.5).toString();
8986
})
8987
8988
g.select('.nv-legendWrap')
8989
.datum(data)
8990
.call(legend);
8991
8992
if ( margin.top != legend.height()) {
8993
margin.top = legend.height();
8994
availableHeight = (height || parseInt(container.style('height')) || 400)
8995
- margin.top - margin.bottom;
8996
}
8997
8998
g.select('.nv-legendWrap')
8999
.attr('transform', 'translate(' + controlWidth() + ',' + (-margin.top) +')');
9000
}
9001
9002
//------------------------------------------------------------
9003
9004
9005
//------------------------------------------------------------
9006
// Controls
9007
9008
if (showControls) {
9009
var controlsData = [
9010
{ key: 'Grouped', disabled: multibar.stacked() },
9011
{ key: 'Stacked', disabled: !multibar.stacked() }
9012
];
9013
9014
controls.width(controlWidth()).color(['#444', '#444', '#444']);
9015
g.select('.nv-controlsWrap')
9016
.datum(controlsData)
9017
.attr('transform', 'translate(0,' + (-margin.top) +')')
9018
.call(controls);
9019
}
9020
9021
//------------------------------------------------------------
9022
9023
9024
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9025
9026
9027
//------------------------------------------------------------
9028
// Main Chart Component(s)
9029
9030
multibar
9031
.disabled(data.map(function(series) { return series.disabled }))
9032
.width(availableWidth)
9033
.height(availableHeight)
9034
.color(data.map(function(d,i) {
9035
return d.color || color(d, i);
9036
}).filter(function(d,i) { return !data[i].disabled }))
9037
9038
9039
var barsWrap = g.select('.nv-barsWrap')
9040
.datum(data.filter(function(d) { return !d.disabled }))
9041
9042
barsWrap.transition().call(multibar);
9043
9044
//------------------------------------------------------------
9045
9046
9047
//------------------------------------------------------------
9048
// Setup Axes
9049
9050
xAxis
9051
.scale(x)
9052
.ticks( availableHeight / 24 )
9053
.tickSize(-availableWidth, 0);
9054
9055
g.select('.nv-x.nv-axis').transition()
9056
.call(xAxis);
9057
9058
var xTicks = g.select('.nv-x.nv-axis').selectAll('g');
9059
9060
xTicks
9061
.selectAll('line, text')
9062
.style('opacity', 1)
9063
9064
9065
yAxis
9066
.scale(y)
9067
.ticks( availableWidth / 100 )
9068
.tickSize( -availableHeight, 0);
9069
9070
g.select('.nv-y.nv-axis')
9071
.attr('transform', 'translate(0,' + availableHeight + ')');
9072
g.select('.nv-y.nv-axis').transition()
9073
.call(yAxis);
9074
9075
//------------------------------------------------------------
9076
9077
9078
9079
//============================================================
9080
// Event Handling/Dispatching (in chart's scope)
9081
//------------------------------------------------------------
9082
9083
legend.dispatch.on('stateChange', function(newState) {
9084
state = newState;
9085
dispatch.stateChange(state);
9086
chart.update();
9087
});
9088
9089
controls.dispatch.on('legendClick', function(d,i) {
9090
if (!d.disabled) return;
9091
controlsData = controlsData.map(function(s) {
9092
s.disabled = true;
9093
return s;
9094
});
9095
d.disabled = false;
9096
9097
switch (d.key) {
9098
case 'Grouped':
9099
multibar.stacked(false);
9100
break;
9101
case 'Stacked':
9102
multibar.stacked(true);
9103
break;
9104
}
9105
9106
state.stacked = multibar.stacked();
9107
dispatch.stateChange(state);
9108
9109
chart.update();
9110
});
9111
9112
dispatch.on('tooltipShow', function(e) {
9113
if (tooltips) showTooltip(e, that.parentNode);
9114
});
9115
9116
// Update chart from a state object passed to event handler
9117
dispatch.on('changeState', function(e) {
9118
9119
if (typeof e.disabled !== 'undefined') {
9120
data.forEach(function(series,i) {
9121
series.disabled = e.disabled[i];
9122
});
9123
9124
state.disabled = e.disabled;
9125
}
9126
9127
if (typeof e.stacked !== 'undefined') {
9128
multibar.stacked(e.stacked);
9129
state.stacked = e.stacked;
9130
}
9131
9132
selection.call(chart);
9133
});
9134
//============================================================
9135
9136
9137
});
9138
9139
return chart;
9140
}
9141
9142
9143
//============================================================
9144
// Event Handling/Dispatching (out of chart's scope)
9145
//------------------------------------------------------------
9146
9147
multibar.dispatch.on('elementMouseover.tooltip', function(e) {
9148
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9149
dispatch.tooltipShow(e);
9150
});
9151
9152
multibar.dispatch.on('elementMouseout.tooltip', function(e) {
9153
dispatch.tooltipHide(e);
9154
});
9155
dispatch.on('tooltipHide', function() {
9156
if (tooltips) nv.tooltip.cleanup();
9157
});
9158
9159
//============================================================
9160
9161
9162
//============================================================
9163
// Expose Public Variables
9164
//------------------------------------------------------------
9165
9166
// expose chart's sub-components
9167
chart.dispatch = dispatch;
9168
chart.multibar = multibar;
9169
chart.legend = legend;
9170
chart.xAxis = xAxis;
9171
chart.yAxis = yAxis;
9172
9173
d3.rebind(chart, multibar, 'x', 'y', 'xDomain', 'yDomain', 'xRange', 'yRange', 'forceX', 'forceY', 'clipEdge', 'id', 'delay', 'showValues', 'valueFormat', 'stacked', 'barColor');
9174
9175
chart.options = nv.utils.optionsFunc.bind(chart);
9176
9177
chart.margin = function(_) {
9178
if (!arguments.length) return margin;
9179
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
9180
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
9181
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
9182
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
9183
return chart;
9184
};
9185
9186
chart.width = function(_) {
9187
if (!arguments.length) return width;
9188
width = _;
9189
return chart;
9190
};
9191
9192
chart.height = function(_) {
9193
if (!arguments.length) return height;
9194
height = _;
9195
return chart;
9196
};
9197
9198
chart.color = function(_) {
9199
if (!arguments.length) return color;
9200
color = nv.utils.getColor(_);
9201
legend.color(color);
9202
return chart;
9203
};
9204
9205
chart.showControls = function(_) {
9206
if (!arguments.length) return showControls;
9207
showControls = _;
9208
return chart;
9209
};
9210
9211
chart.showLegend = function(_) {
9212
if (!arguments.length) return showLegend;
9213
showLegend = _;
9214
return chart;
9215
};
9216
9217
chart.tooltip = function(_) {
9218
if (!arguments.length) return tooltip;
9219
tooltip = _;
9220
return chart;
9221
};
9222
9223
chart.tooltips = function(_) {
9224
if (!arguments.length) return tooltips;
9225
tooltips = _;
9226
return chart;
9227
};
9228
9229
chart.tooltipContent = function(_) {
9230
if (!arguments.length) return tooltip;
9231
tooltip = _;
9232
return chart;
9233
};
9234
9235
chart.state = function(_) {
9236
if (!arguments.length) return state;
9237
state = _;
9238
return chart;
9239
};
9240
9241
chart.defaultState = function(_) {
9242
if (!arguments.length) return defaultState;
9243
defaultState = _;
9244
return chart;
9245
};
9246
9247
chart.noData = function(_) {
9248
if (!arguments.length) return noData;
9249
noData = _;
9250
return chart;
9251
};
9252
9253
chart.transitionDuration = function(_) {
9254
if (!arguments.length) return transitionDuration;
9255
transitionDuration = _;
9256
return chart;
9257
};
9258
//============================================================
9259
9260
9261
return chart;
9262
}
9263
nv.models.multiChart = function() {
9264
"use strict";
9265
//============================================================
9266
// Public Variables with Default Settings
9267
//------------------------------------------------------------
9268
9269
var margin = {top: 30, right: 20, bottom: 50, left: 60},
9270
color = d3.scale.category20().range(),
9271
width = null,
9272
height = null,
9273
showLegend = true,
9274
tooltips = true,
9275
tooltip = function(key, x, y, e, graph) {
9276
return '<h3>' + key + '</h3>' +
9277
'<p>' + y + ' at ' + x + '</p>'
9278
},
9279
x,
9280
y,
9281
yDomain1,
9282
yDomain2
9283
; //can be accessed via chart.lines.[x/y]Scale()
9284
9285
//============================================================
9286
// Private Variables
9287
//------------------------------------------------------------
9288
9289
var x = d3.scale.linear(),
9290
yScale1 = d3.scale.linear(),
9291
yScale2 = d3.scale.linear(),
9292
9293
lines1 = nv.models.line().yScale(yScale1),
9294
lines2 = nv.models.line().yScale(yScale2),
9295
9296
bars1 = nv.models.multiBar().stacked(false).yScale(yScale1),
9297
bars2 = nv.models.multiBar().stacked(false).yScale(yScale2),
9298
9299
stack1 = nv.models.stackedArea().yScale(yScale1),
9300
stack2 = nv.models.stackedArea().yScale(yScale2),
9301
9302
xAxis = nv.models.axis().scale(x).orient('bottom').tickPadding(5),
9303
yAxis1 = nv.models.axis().scale(yScale1).orient('left'),
9304
yAxis2 = nv.models.axis().scale(yScale2).orient('right'),
9305
9306
legend = nv.models.legend().height(30),
9307
dispatch = d3.dispatch('tooltipShow', 'tooltipHide');
9308
9309
var showTooltip = function(e, offsetElement) {
9310
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
9311
top = e.pos[1] + ( offsetElement.offsetTop || 0),
9312
x = xAxis.tickFormat()(lines1.x()(e.point, e.pointIndex)),
9313
y = ((e.series.yAxis == 2) ? yAxis2 : yAxis1).tickFormat()(lines1.y()(e.point, e.pointIndex)),
9314
content = tooltip(e.series.key, x, y, e, chart);
9315
9316
nv.tooltip.show([left, top], content, undefined, undefined, offsetElement.offsetParent);
9317
};
9318
9319
function chart(selection) {
9320
selection.each(function(data) {
9321
var container = d3.select(this),
9322
that = this;
9323
9324
chart.update = function() { container.transition().call(chart); };
9325
chart.container = this;
9326
9327
var availableWidth = (width || parseInt(container.style('width')) || 960)
9328
- margin.left - margin.right,
9329
availableHeight = (height || parseInt(container.style('height')) || 400)
9330
- margin.top - margin.bottom;
9331
9332
var dataLines1 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 1})
9333
var dataLines2 = data.filter(function(d) {return !d.disabled && d.type == 'line' && d.yAxis == 2})
9334
var dataBars1 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 1})
9335
var dataBars2 = data.filter(function(d) {return !d.disabled && d.type == 'bar' && d.yAxis == 2})
9336
var dataStack1 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 1})
9337
var dataStack2 = data.filter(function(d) {return !d.disabled && d.type == 'area' && d.yAxis == 2})
9338
9339
var series1 = data.filter(function(d) {return !d.disabled && d.yAxis == 1})
9340
.map(function(d) {
9341
return d.values.map(function(d,i) {
9342
return { x: d.x, y: d.y }
9343
})
9344
})
9345
9346
var series2 = data.filter(function(d) {return !d.disabled && d.yAxis == 2})
9347
.map(function(d) {
9348
return d.values.map(function(d,i) {
9349
return { x: d.x, y: d.y }
9350
})
9351
})
9352
9353
x .domain(d3.extent(d3.merge(series1.concat(series2)), function(d) { return d.x } ))
9354
.range([0, availableWidth]);
9355
9356
var wrap = container.selectAll('g.wrap.multiChart').data([data]);
9357
var gEnter = wrap.enter().append('g').attr('class', 'wrap nvd3 multiChart').append('g');
9358
9359
gEnter.append('g').attr('class', 'x axis');
9360
gEnter.append('g').attr('class', 'y1 axis');
9361
gEnter.append('g').attr('class', 'y2 axis');
9362
gEnter.append('g').attr('class', 'lines1Wrap');
9363
gEnter.append('g').attr('class', 'lines2Wrap');
9364
gEnter.append('g').attr('class', 'bars1Wrap');
9365
gEnter.append('g').attr('class', 'bars2Wrap');
9366
gEnter.append('g').attr('class', 'stack1Wrap');
9367
gEnter.append('g').attr('class', 'stack2Wrap');
9368
gEnter.append('g').attr('class', 'legendWrap');
9369
9370
var g = wrap.select('g');
9371
9372
if (showLegend) {
9373
legend.width( availableWidth / 2 );
9374
9375
g.select('.legendWrap')
9376
.datum(data.map(function(series) {
9377
series.originalKey = series.originalKey === undefined ? series.key : series.originalKey;
9378
series.key = series.originalKey + (series.yAxis == 1 ? '' : ' (right axis)');
9379
return series;
9380
}))
9381
.call(legend);
9382
9383
if ( margin.top != legend.height()) {
9384
margin.top = legend.height();
9385
availableHeight = (height || parseInt(container.style('height')) || 400)
9386
- margin.top - margin.bottom;
9387
}
9388
9389
g.select('.legendWrap')
9390
.attr('transform', 'translate(' + ( availableWidth / 2 ) + ',' + (-margin.top) +')');
9391
}
9392
9393
9394
lines1
9395
.width(availableWidth)
9396
.height(availableHeight)
9397
.interpolate("monotone")
9398
.color(data.map(function(d,i) {
9399
return d.color || color[i % color.length];
9400
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'line'}));
9401
9402
lines2
9403
.width(availableWidth)
9404
.height(availableHeight)
9405
.interpolate("monotone")
9406
.color(data.map(function(d,i) {
9407
return d.color || color[i % color.length];
9408
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'line'}));
9409
9410
bars1
9411
.width(availableWidth)
9412
.height(availableHeight)
9413
.color(data.map(function(d,i) {
9414
return d.color || color[i % color.length];
9415
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'bar'}));
9416
9417
bars2
9418
.width(availableWidth)
9419
.height(availableHeight)
9420
.color(data.map(function(d,i) {
9421
return d.color || color[i % color.length];
9422
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'bar'}));
9423
9424
stack1
9425
.width(availableWidth)
9426
.height(availableHeight)
9427
.color(data.map(function(d,i) {
9428
return d.color || color[i % color.length];
9429
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 1 && data[i].type == 'area'}));
9430
9431
stack2
9432
.width(availableWidth)
9433
.height(availableHeight)
9434
.color(data.map(function(d,i) {
9435
return d.color || color[i % color.length];
9436
}).filter(function(d,i) { return !data[i].disabled && data[i].yAxis == 2 && data[i].type == 'area'}));
9437
9438
g.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9439
9440
9441
var lines1Wrap = g.select('.lines1Wrap')
9442
.datum(dataLines1)
9443
var bars1Wrap = g.select('.bars1Wrap')
9444
.datum(dataBars1)
9445
var stack1Wrap = g.select('.stack1Wrap')
9446
.datum(dataStack1)
9447
9448
var lines2Wrap = g.select('.lines2Wrap')
9449
.datum(dataLines2)
9450
var bars2Wrap = g.select('.bars2Wrap')
9451
.datum(dataBars2)
9452
var stack2Wrap = g.select('.stack2Wrap')
9453
.datum(dataStack2)
9454
9455
var extraValue1 = dataStack1.length ? dataStack1.map(function(a){return a.values}).reduce(function(a,b){
9456
return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9457
}).concat([{x:0, y:0}]) : []
9458
var extraValue2 = dataStack2.length ? dataStack2.map(function(a){return a.values}).reduce(function(a,b){
9459
return a.map(function(aVal,i){return {x: aVal.x, y: aVal.y + b[i].y}})
9460
}).concat([{x:0, y:0}]) : []
9461
9462
yScale1 .domain(yDomain1 || d3.extent(d3.merge(series1).concat(extraValue1), function(d) { return d.y } ))
9463
.range([0, availableHeight])
9464
9465
yScale2 .domain(yDomain2 || d3.extent(d3.merge(series2).concat(extraValue2), function(d) { return d.y } ))
9466
.range([0, availableHeight])
9467
9468
lines1.yDomain(yScale1.domain())
9469
bars1.yDomain(yScale1.domain())
9470
stack1.yDomain(yScale1.domain())
9471
9472
lines2.yDomain(yScale2.domain())
9473
bars2.yDomain(yScale2.domain())
9474
stack2.yDomain(yScale2.domain())
9475
9476
if(dataStack1.length){d3.transition(stack1Wrap).call(stack1);}
9477
if(dataStack2.length){d3.transition(stack2Wrap).call(stack2);}
9478
9479
if(dataBars1.length){d3.transition(bars1Wrap).call(bars1);}
9480
if(dataBars2.length){d3.transition(bars2Wrap).call(bars2);}
9481
9482
if(dataLines1.length){d3.transition(lines1Wrap).call(lines1);}
9483
if(dataLines2.length){d3.transition(lines2Wrap).call(lines2);}
9484
9485
9486
9487
xAxis
9488
.ticks( availableWidth / 100 )
9489
.tickSize(-availableHeight, 0);
9490
9491
g.select('.x.axis')
9492
.attr('transform', 'translate(0,' + availableHeight + ')');
9493
d3.transition(g.select('.x.axis'))
9494
.call(xAxis);
9495
9496
yAxis1
9497
.ticks( availableHeight / 36 )
9498
.tickSize( -availableWidth, 0);
9499
9500
9501
d3.transition(g.select('.y1.axis'))
9502
.call(yAxis1);
9503
9504
yAxis2
9505
.ticks( availableHeight / 36 )
9506
.tickSize( -availableWidth, 0);
9507
9508
d3.transition(g.select('.y2.axis'))
9509
.call(yAxis2);
9510
9511
g.select('.y2.axis')
9512
.style('opacity', series2.length ? 1 : 0)
9513
.attr('transform', 'translate(' + x.range()[1] + ',0)');
9514
9515
legend.dispatch.on('stateChange', function(newState) {
9516
chart.update();
9517
});
9518
9519
dispatch.on('tooltipShow', function(e) {
9520
if (tooltips) showTooltip(e, that.parentNode);
9521
});
9522
9523
});
9524
9525
return chart;
9526
}
9527
9528
9529
//============================================================
9530
// Event Handling/Dispatching (out of chart's scope)
9531
//------------------------------------------------------------
9532
9533
lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9534
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9535
dispatch.tooltipShow(e);
9536
});
9537
9538
lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9539
dispatch.tooltipHide(e);
9540
});
9541
9542
lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9543
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9544
dispatch.tooltipShow(e);
9545
});
9546
9547
lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9548
dispatch.tooltipHide(e);
9549
});
9550
9551
bars1.dispatch.on('elementMouseover.tooltip', function(e) {
9552
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9553
dispatch.tooltipShow(e);
9554
});
9555
9556
bars1.dispatch.on('elementMouseout.tooltip', function(e) {
9557
dispatch.tooltipHide(e);
9558
});
9559
9560
bars2.dispatch.on('elementMouseover.tooltip', function(e) {
9561
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9562
dispatch.tooltipShow(e);
9563
});
9564
9565
bars2.dispatch.on('elementMouseout.tooltip', function(e) {
9566
dispatch.tooltipHide(e);
9567
});
9568
9569
stack1.dispatch.on('tooltipShow', function(e) {
9570
//disable tooltips when value ~= 0
9571
//// TODO: consider removing points from voronoi that have 0 value instead of this hack
9572
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
9573
setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9574
return false;
9575
}
9576
9577
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9578
dispatch.tooltipShow(e);
9579
});
9580
9581
stack1.dispatch.on('tooltipHide', function(e) {
9582
dispatch.tooltipHide(e);
9583
});
9584
9585
stack2.dispatch.on('tooltipShow', function(e) {
9586
//disable tooltips when value ~= 0
9587
//// TODO: consider removing points from voronoi that have 0 value instead of this hack
9588
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
9589
setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
9590
return false;
9591
}
9592
9593
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
9594
dispatch.tooltipShow(e);
9595
});
9596
9597
stack2.dispatch.on('tooltipHide', function(e) {
9598
dispatch.tooltipHide(e);
9599
});
9600
9601
lines1.dispatch.on('elementMouseover.tooltip', function(e) {
9602
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9603
dispatch.tooltipShow(e);
9604
});
9605
9606
lines1.dispatch.on('elementMouseout.tooltip', function(e) {
9607
dispatch.tooltipHide(e);
9608
});
9609
9610
lines2.dispatch.on('elementMouseover.tooltip', function(e) {
9611
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
9612
dispatch.tooltipShow(e);
9613
});
9614
9615
lines2.dispatch.on('elementMouseout.tooltip', function(e) {
9616
dispatch.tooltipHide(e);
9617
});
9618
9619
dispatch.on('tooltipHide', function() {
9620
if (tooltips) nv.tooltip.cleanup();
9621
});
9622
9623
9624
9625
//============================================================
9626
// Global getters and setters
9627
//------------------------------------------------------------
9628
9629
chart.dispatch = dispatch;
9630
chart.lines1 = lines1;
9631
chart.lines2 = lines2;
9632
chart.bars1 = bars1;
9633
chart.bars2 = bars2;
9634
chart.stack1 = stack1;
9635
chart.stack2 = stack2;
9636
chart.xAxis = xAxis;
9637
chart.yAxis1 = yAxis1;
9638
chart.yAxis2 = yAxis2;
9639
chart.options = nv.utils.optionsFunc.bind(chart);
9640
9641
chart.x = function(_) {
9642
if (!arguments.length) return getX;
9643
getX = _;
9644
lines1.x(_);
9645
bars1.x(_);
9646
return chart;
9647
};
9648
9649
chart.y = function(_) {
9650
if (!arguments.length) return getY;
9651
getY = _;
9652
lines1.y(_);
9653
bars1.y(_);
9654
return chart;
9655
};
9656
9657
chart.yDomain1 = function(_) {
9658
if (!arguments.length) return yDomain1;
9659
yDomain1 = _;
9660
return chart;
9661
};
9662
9663
chart.yDomain2 = function(_) {
9664
if (!arguments.length) return yDomain2;
9665
yDomain2 = _;
9666
return chart;
9667
};
9668
9669
chart.margin = function(_) {
9670
if (!arguments.length) return margin;
9671
margin = _;
9672
return chart;
9673
};
9674
9675
chart.width = function(_) {
9676
if (!arguments.length) return width;
9677
width = _;
9678
return chart;
9679
};
9680
9681
chart.height = function(_) {
9682
if (!arguments.length) return height;
9683
height = _;
9684
return chart;
9685
};
9686
9687
chart.color = function(_) {
9688
if (!arguments.length) return color;
9689
color = _;
9690
legend.color(_);
9691
return chart;
9692
};
9693
9694
chart.showLegend = function(_) {
9695
if (!arguments.length) return showLegend;
9696
showLegend = _;
9697
return chart;
9698
};
9699
9700
chart.tooltips = function(_) {
9701
if (!arguments.length) return tooltips;
9702
tooltips = _;
9703
return chart;
9704
};
9705
9706
chart.tooltipContent = function(_) {
9707
if (!arguments.length) return tooltip;
9708
tooltip = _;
9709
return chart;
9710
};
9711
9712
return chart;
9713
}
9714
9715
9716
nv.models.ohlcBar = function() {
9717
"use strict";
9718
//============================================================
9719
// Public Variables with Default Settings
9720
//------------------------------------------------------------
9721
9722
var margin = {top: 0, right: 0, bottom: 0, left: 0}
9723
, width = 960
9724
, height = 500
9725
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
9726
, x = d3.scale.linear()
9727
, y = d3.scale.linear()
9728
, getX = function(d) { return d.x }
9729
, getY = function(d) { return d.y }
9730
, getOpen = function(d) { return d.open }
9731
, getClose = function(d) { return d.close }
9732
, getHigh = function(d) { return d.high }
9733
, getLow = function(d) { return d.low }
9734
, forceX = []
9735
, forceY = []
9736
, padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
9737
, clipEdge = true
9738
, color = nv.utils.defaultColor()
9739
, xDomain
9740
, yDomain
9741
, xRange
9742
, yRange
9743
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
9744
;
9745
9746
//============================================================
9747
9748
//============================================================
9749
// Private Variables
9750
//------------------------------------------------------------
9751
9752
//TODO: store old scales for transitions
9753
9754
//============================================================
9755
9756
9757
function chart(selection) {
9758
selection.each(function(data) {
9759
var availableWidth = width - margin.left - margin.right,
9760
availableHeight = height - margin.top - margin.bottom,
9761
container = d3.select(this);
9762
9763
9764
//------------------------------------------------------------
9765
// Setup Scales
9766
9767
x .domain(xDomain || d3.extent(data[0].values.map(getX).concat(forceX) ));
9768
9769
if (padData)
9770
x.range(xRange || [availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
9771
else
9772
x.range(xRange || [0, availableWidth]);
9773
9774
y .domain(yDomain || [
9775
d3.min(data[0].values.map(getLow).concat(forceY)),
9776
d3.max(data[0].values.map(getHigh).concat(forceY))
9777
])
9778
.range(yRange || [availableHeight, 0]);
9779
9780
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
9781
if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
9782
if (x.domain()[0] === x.domain()[1])
9783
x.domain()[0] ?
9784
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
9785
: x.domain([-1,1]);
9786
9787
if (y.domain()[0] === y.domain()[1])
9788
y.domain()[0] ?
9789
y.domain([y.domain()[0] + y.domain()[0] * 0.01, y.domain()[1] - y.domain()[1] * 0.01])
9790
: y.domain([-1,1]);
9791
9792
//------------------------------------------------------------
9793
9794
9795
//------------------------------------------------------------
9796
// Setup containers and skeleton of chart
9797
9798
var wrap = d3.select(this).selectAll('g.nv-wrap.nv-ohlcBar').data([data[0].values]);
9799
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-ohlcBar');
9800
var defsEnter = wrapEnter.append('defs');
9801
var gEnter = wrapEnter.append('g');
9802
var g = wrap.select('g');
9803
9804
gEnter.append('g').attr('class', 'nv-ticks');
9805
9806
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
9807
9808
//------------------------------------------------------------
9809
9810
9811
container
9812
.on('click', function(d,i) {
9813
dispatch.chartClick({
9814
data: d,
9815
index: i,
9816
pos: d3.event,
9817
id: id
9818
});
9819
});
9820
9821
9822
defsEnter.append('clipPath')
9823
.attr('id', 'nv-chart-clip-path-' + id)
9824
.append('rect');
9825
9826
wrap.select('#nv-chart-clip-path-' + id + ' rect')
9827
.attr('width', availableWidth)
9828
.attr('height', availableHeight);
9829
9830
g .attr('clip-path', clipEdge ? 'url(#nv-chart-clip-path-' + id + ')' : '');
9831
9832
9833
9834
var ticks = wrap.select('.nv-ticks').selectAll('.nv-tick')
9835
.data(function(d) { return d });
9836
9837
ticks.exit().remove();
9838
9839
9840
var ticksEnter = ticks.enter().append('path')
9841
.attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9842
.attr('d', function(d,i) {
9843
var w = (availableWidth / data[0].values.length) * .9;
9844
return 'm0,0l0,'
9845
+ (y(getOpen(d,i))
9846
- y(getHigh(d,i)))
9847
+ 'l'
9848
+ (-w/2)
9849
+ ',0l'
9850
+ (w/2)
9851
+ ',0l0,'
9852
+ (y(getLow(d,i)) - y(getOpen(d,i)))
9853
+ 'l0,'
9854
+ (y(getClose(d,i))
9855
- y(getLow(d,i)))
9856
+ 'l'
9857
+ (w/2)
9858
+ ',0l'
9859
+ (-w/2)
9860
+ ',0z';
9861
})
9862
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9863
//.attr('fill', function(d,i) { return color[0]; })
9864
//.attr('stroke', function(d,i) { return color[0]; })
9865
//.attr('x', 0 )
9866
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
9867
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) })
9868
.on('mouseover', function(d,i) {
9869
d3.select(this).classed('hover', true);
9870
dispatch.elementMouseover({
9871
point: d,
9872
series: data[0],
9873
pos: [x(getX(d,i)), y(getY(d,i))], // TODO: Figure out why the value appears to be shifted
9874
pointIndex: i,
9875
seriesIndex: 0,
9876
e: d3.event
9877
});
9878
9879
})
9880
.on('mouseout', function(d,i) {
9881
d3.select(this).classed('hover', false);
9882
dispatch.elementMouseout({
9883
point: d,
9884
series: data[0],
9885
pointIndex: i,
9886
seriesIndex: 0,
9887
e: d3.event
9888
});
9889
})
9890
.on('click', function(d,i) {
9891
dispatch.elementClick({
9892
//label: d[label],
9893
value: getY(d,i),
9894
data: d,
9895
index: i,
9896
pos: [x(getX(d,i)), y(getY(d,i))],
9897
e: d3.event,
9898
id: id
9899
});
9900
d3.event.stopPropagation();
9901
})
9902
.on('dblclick', function(d,i) {
9903
dispatch.elementDblClick({
9904
//label: d[label],
9905
value: getY(d,i),
9906
data: d,
9907
index: i,
9908
pos: [x(getX(d,i)), y(getY(d,i))],
9909
e: d3.event,
9910
id: id
9911
});
9912
d3.event.stopPropagation();
9913
});
9914
9915
ticks
9916
.attr('class', function(d,i,j) { return (getOpen(d,i) > getClose(d,i) ? 'nv-tick negative' : 'nv-tick positive') + ' nv-tick-' + j + '-' + i })
9917
d3.transition(ticks)
9918
.attr('transform', function(d,i) { return 'translate(' + x(getX(d,i)) + ',' + y(getHigh(d,i)) + ')'; })
9919
.attr('d', function(d,i) {
9920
var w = (availableWidth / data[0].values.length) * .9;
9921
return 'm0,0l0,'
9922
+ (y(getOpen(d,i))
9923
- y(getHigh(d,i)))
9924
+ 'l'
9925
+ (-w/2)
9926
+ ',0l'
9927
+ (w/2)
9928
+ ',0l0,'
9929
+ (y(getLow(d,i))
9930
- y(getOpen(d,i)))
9931
+ 'l0,'
9932
+ (y(getClose(d,i))
9933
- y(getLow(d,i)))
9934
+ 'l'
9935
+ (w/2)
9936
+ ',0l'
9937
+ (-w/2)
9938
+ ',0z';
9939
})
9940
//.attr('width', (availableWidth / data[0].values.length) * .9 )
9941
9942
9943
//d3.transition(ticks)
9944
//.attr('y', function(d,i) { return y(Math.max(0, getY(d,i))) })
9945
//.attr('height', function(d,i) { return Math.abs(y(getY(d,i)) - y(0)) });
9946
//.order(); // not sure if this makes any sense for this model
9947
9948
});
9949
9950
return chart;
9951
}
9952
9953
9954
//============================================================
9955
// Expose Public Variables
9956
//------------------------------------------------------------
9957
9958
chart.dispatch = dispatch;
9959
9960
chart.options = nv.utils.optionsFunc.bind(chart);
9961
9962
chart.x = function(_) {
9963
if (!arguments.length) return getX;
9964
getX = _;
9965
return chart;
9966
};
9967
9968
chart.y = function(_) {
9969
if (!arguments.length) return getY;
9970
getY = _;
9971
return chart;
9972
};
9973
9974
chart.open = function(_) {
9975
if (!arguments.length) return getOpen;
9976
getOpen = _;
9977
return chart;
9978
};
9979
9980
chart.close = function(_) {
9981
if (!arguments.length) return getClose;
9982
getClose = _;
9983
return chart;
9984
};
9985
9986
chart.high = function(_) {
9987
if (!arguments.length) return getHigh;
9988
getHigh = _;
9989
return chart;
9990
};
9991
9992
chart.low = function(_) {
9993
if (!arguments.length) return getLow;
9994
getLow = _;
9995
return chart;
9996
};
9997
9998
chart.margin = function(_) {
9999
if (!arguments.length) return margin;
10000
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10001
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10002
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10003
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10004
return chart;
10005
};
10006
10007
chart.width = function(_) {
10008
if (!arguments.length) return width;
10009
width = _;
10010
return chart;
10011
};
10012
10013
chart.height = function(_) {
10014
if (!arguments.length) return height;
10015
height = _;
10016
return chart;
10017
};
10018
10019
chart.xScale = function(_) {
10020
if (!arguments.length) return x;
10021
x = _;
10022
return chart;
10023
};
10024
10025
chart.yScale = function(_) {
10026
if (!arguments.length) return y;
10027
y = _;
10028
return chart;
10029
};
10030
10031
chart.xDomain = function(_) {
10032
if (!arguments.length) return xDomain;
10033
xDomain = _;
10034
return chart;
10035
};
10036
10037
chart.yDomain = function(_) {
10038
if (!arguments.length) return yDomain;
10039
yDomain = _;
10040
return chart;
10041
};
10042
10043
chart.xRange = function(_) {
10044
if (!arguments.length) return xRange;
10045
xRange = _;
10046
return chart;
10047
};
10048
10049
chart.yRange = function(_) {
10050
if (!arguments.length) return yRange;
10051
yRange = _;
10052
return chart;
10053
};
10054
10055
chart.forceX = function(_) {
10056
if (!arguments.length) return forceX;
10057
forceX = _;
10058
return chart;
10059
};
10060
10061
chart.forceY = function(_) {
10062
if (!arguments.length) return forceY;
10063
forceY = _;
10064
return chart;
10065
};
10066
10067
chart.padData = function(_) {
10068
if (!arguments.length) return padData;
10069
padData = _;
10070
return chart;
10071
};
10072
10073
chart.clipEdge = function(_) {
10074
if (!arguments.length) return clipEdge;
10075
clipEdge = _;
10076
return chart;
10077
};
10078
10079
chart.color = function(_) {
10080
if (!arguments.length) return color;
10081
color = nv.utils.getColor(_);
10082
return chart;
10083
};
10084
10085
chart.id = function(_) {
10086
if (!arguments.length) return id;
10087
id = _;
10088
return chart;
10089
};
10090
10091
//============================================================
10092
10093
10094
return chart;
10095
}
10096
nv.models.pie = function() {
10097
"use strict";
10098
//============================================================
10099
// Public Variables with Default Settings
10100
//------------------------------------------------------------
10101
10102
var margin = {top: 0, right: 0, bottom: 0, left: 0}
10103
, width = 500
10104
, height = 500
10105
, getX = function(d) { return d.x }
10106
, getY = function(d) { return d.y }
10107
, getDescription = function(d) { return d.description }
10108
, id = Math.floor(Math.random() * 10000) //Create semi-unique ID in case user doesn't select one
10109
, color = nv.utils.defaultColor()
10110
, valueFormat = d3.format(',.2f')
10111
, showLabels = true
10112
, pieLabelsOutside = true
10113
, donutLabelsOutside = false
10114
, labelType = "key"
10115
, labelThreshold = .02 //if slice percentage is under this, don't show label
10116
, donut = false
10117
, labelSunbeamLayout = false
10118
, startAngle = false
10119
, endAngle = false
10120
, donutRatio = 0.5
10121
, dispatch = d3.dispatch('chartClick', 'elementClick', 'elementDblClick', 'elementMouseover', 'elementMouseout')
10122
;
10123
10124
//============================================================
10125
10126
10127
function chart(selection) {
10128
selection.each(function(data) {
10129
var availableWidth = width - margin.left - margin.right,
10130
availableHeight = height - margin.top - margin.bottom,
10131
radius = Math.min(availableWidth, availableHeight) / 2,
10132
arcRadius = radius-(radius / 5),
10133
container = d3.select(this);
10134
10135
10136
//------------------------------------------------------------
10137
// Setup containers and skeleton of chart
10138
10139
//var wrap = container.selectAll('.nv-wrap.nv-pie').data([data]);
10140
var wrap = container.selectAll('.nv-wrap.nv-pie').data(data);
10141
var wrapEnter = wrap.enter().append('g').attr('class','nvd3 nv-wrap nv-pie nv-chart-' + id);
10142
var gEnter = wrapEnter.append('g');
10143
var g = wrap.select('g');
10144
10145
gEnter.append('g').attr('class', 'nv-pie');
10146
10147
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10148
g.select('.nv-pie').attr('transform', 'translate(' + availableWidth / 2 + ',' + availableHeight / 2 + ')');
10149
10150
//------------------------------------------------------------
10151
10152
10153
container
10154
.on('click', function(d,i) {
10155
dispatch.chartClick({
10156
data: d,
10157
index: i,
10158
pos: d3.event,
10159
id: id
10160
});
10161
});
10162
10163
10164
var arc = d3.svg.arc()
10165
.outerRadius(arcRadius);
10166
10167
if (startAngle) arc.startAngle(startAngle)
10168
if (endAngle) arc.endAngle(endAngle);
10169
if (donut) arc.innerRadius(radius * donutRatio);
10170
10171
// Setup the Pie chart and choose the data element
10172
var pie = d3.layout.pie()
10173
.sort(null)
10174
.value(function(d) { return d.disabled ? 0 : getY(d) });
10175
10176
var slices = wrap.select('.nv-pie').selectAll('.nv-slice')
10177
.data(pie);
10178
10179
slices.exit().remove();
10180
10181
var ae = slices.enter().append('g')
10182
.attr('class', 'nv-slice')
10183
.on('mouseover', function(d,i){
10184
d3.select(this).classed('hover', true);
10185
dispatch.elementMouseover({
10186
label: getX(d.data),
10187
value: getY(d.data),
10188
point: d.data,
10189
pointIndex: i,
10190
pos: [d3.event.pageX, d3.event.pageY],
10191
id: id
10192
});
10193
})
10194
.on('mouseout', function(d,i){
10195
d3.select(this).classed('hover', false);
10196
dispatch.elementMouseout({
10197
label: getX(d.data),
10198
value: getY(d.data),
10199
point: d.data,
10200
index: i,
10201
id: id
10202
});
10203
})
10204
.on('click', function(d,i) {
10205
dispatch.elementClick({
10206
label: getX(d.data),
10207
value: getY(d.data),
10208
point: d.data,
10209
index: i,
10210
pos: d3.event,
10211
id: id
10212
});
10213
d3.event.stopPropagation();
10214
})
10215
.on('dblclick', function(d,i) {
10216
dispatch.elementDblClick({
10217
label: getX(d.data),
10218
value: getY(d.data),
10219
point: d.data,
10220
index: i,
10221
pos: d3.event,
10222
id: id
10223
});
10224
d3.event.stopPropagation();
10225
});
10226
10227
slices
10228
.attr('fill', function(d,i) { return color(d, i); })
10229
.attr('stroke', function(d,i) { return color(d, i); });
10230
10231
var paths = ae.append('path')
10232
.each(function(d) { this._current = d; });
10233
//.attr('d', arc);
10234
10235
d3.transition(slices.select('path'))
10236
.attr('d', arc)
10237
.attrTween('d', arcTween);
10238
10239
if (showLabels) {
10240
// This does the normal label
10241
var labelsArc = d3.svg.arc().innerRadius(0);
10242
10243
if (pieLabelsOutside){ labelsArc = arc; }
10244
10245
if (donutLabelsOutside) { labelsArc = d3.svg.arc().outerRadius(arc.outerRadius()); }
10246
10247
ae.append("g").classed("nv-label", true)
10248
.each(function(d, i) {
10249
var group = d3.select(this);
10250
10251
group
10252
.attr('transform', function(d) {
10253
if (labelSunbeamLayout) {
10254
d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10255
d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10256
var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10257
if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10258
rotateAngle -= 90;
10259
} else {
10260
rotateAngle += 90;
10261
}
10262
return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10263
} else {
10264
d.outerRadius = radius + 10; // Set Outer Coordinate
10265
d.innerRadius = radius + 15; // Set Inner Coordinate
10266
return 'translate(' + labelsArc.centroid(d) + ')'
10267
}
10268
});
10269
10270
group.append('rect')
10271
.style('stroke', '#fff')
10272
.style('fill', '#fff')
10273
.attr("rx", 3)
10274
.attr("ry", 3);
10275
10276
group.append('text')
10277
.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
10278
.style('fill', '#000')
10279
10280
10281
});
10282
10283
slices.select(".nv-label").transition()
10284
.attr('transform', function(d) {
10285
if (labelSunbeamLayout) {
10286
d.outerRadius = arcRadius + 10; // Set Outer Coordinate
10287
d.innerRadius = arcRadius + 15; // Set Inner Coordinate
10288
var rotateAngle = (d.startAngle + d.endAngle) / 2 * (180 / Math.PI);
10289
if ((d.startAngle+d.endAngle)/2 < Math.PI) {
10290
rotateAngle -= 90;
10291
} else {
10292
rotateAngle += 90;
10293
}
10294
return 'translate(' + labelsArc.centroid(d) + ') rotate(' + rotateAngle + ')';
10295
} else {
10296
d.outerRadius = radius + 10; // Set Outer Coordinate
10297
d.innerRadius = radius + 15; // Set Inner Coordinate
10298
return 'translate(' + labelsArc.centroid(d) + ')'
10299
}
10300
});
10301
10302
slices.each(function(d, i) {
10303
var slice = d3.select(this);
10304
10305
slice
10306
.select(".nv-label text")
10307
.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
10308
.text(function(d, i) {
10309
var percent = (d.endAngle - d.startAngle) / (2 * Math.PI);
10310
var labelTypes = {
10311
"key" : getX(d.data),
10312
"value": getY(d.data),
10313
"percent": d3.format('%')(percent)
10314
};
10315
return (d.value && percent > labelThreshold) ? labelTypes[labelType] : '';
10316
});
10317
10318
var textBox = slice.select('text').node().getBBox();
10319
slice.select(".nv-label rect")
10320
.attr("width", textBox.width + 10)
10321
.attr("height", textBox.height + 10)
10322
.attr("transform", function() {
10323
return "translate(" + [textBox.x - 5, textBox.y - 5] + ")";
10324
});
10325
});
10326
}
10327
10328
10329
// Computes the angle of an arc, converting from radians to degrees.
10330
function angle(d) {
10331
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
10332
return a > 90 ? a - 180 : a;
10333
}
10334
10335
function arcTween(a) {
10336
a.endAngle = isNaN(a.endAngle) ? 0 : a.endAngle;
10337
a.startAngle = isNaN(a.startAngle) ? 0 : a.startAngle;
10338
if (!donut) a.innerRadius = 0;
10339
var i = d3.interpolate(this._current, a);
10340
this._current = i(0);
10341
return function(t) {
10342
return arc(i(t));
10343
};
10344
}
10345
10346
function tweenPie(b) {
10347
b.innerRadius = 0;
10348
var i = d3.interpolate({startAngle: 0, endAngle: 0}, b);
10349
return function(t) {
10350
return arc(i(t));
10351
};
10352
}
10353
10354
});
10355
10356
return chart;
10357
}
10358
10359
10360
//============================================================
10361
// Expose Public Variables
10362
//------------------------------------------------------------
10363
10364
chart.dispatch = dispatch;
10365
chart.options = nv.utils.optionsFunc.bind(chart);
10366
10367
chart.margin = function(_) {
10368
if (!arguments.length) return margin;
10369
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10370
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10371
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10372
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10373
return chart;
10374
};
10375
10376
chart.width = function(_) {
10377
if (!arguments.length) return width;
10378
width = _;
10379
return chart;
10380
};
10381
10382
chart.height = function(_) {
10383
if (!arguments.length) return height;
10384
height = _;
10385
return chart;
10386
};
10387
10388
chart.values = function(_) {
10389
nv.log("pie.values() is no longer supported.");
10390
return chart;
10391
};
10392
10393
chart.x = function(_) {
10394
if (!arguments.length) return getX;
10395
getX = _;
10396
return chart;
10397
};
10398
10399
chart.y = function(_) {
10400
if (!arguments.length) return getY;
10401
getY = d3.functor(_);
10402
return chart;
10403
};
10404
10405
chart.description = function(_) {
10406
if (!arguments.length) return getDescription;
10407
getDescription = _;
10408
return chart;
10409
};
10410
10411
chart.showLabels = function(_) {
10412
if (!arguments.length) return showLabels;
10413
showLabels = _;
10414
return chart;
10415
};
10416
10417
chart.labelSunbeamLayout = function(_) {
10418
if (!arguments.length) return labelSunbeamLayout;
10419
labelSunbeamLayout = _;
10420
return chart;
10421
};
10422
10423
chart.donutLabelsOutside = function(_) {
10424
if (!arguments.length) return donutLabelsOutside;
10425
donutLabelsOutside = _;
10426
return chart;
10427
};
10428
10429
chart.pieLabelsOutside = function(_) {
10430
if (!arguments.length) return pieLabelsOutside;
10431
pieLabelsOutside = _;
10432
return chart;
10433
};
10434
10435
chart.labelType = function(_) {
10436
if (!arguments.length) return labelType;
10437
labelType = _;
10438
labelType = labelType || "key";
10439
return chart;
10440
};
10441
10442
chart.donut = function(_) {
10443
if (!arguments.length) return donut;
10444
donut = _;
10445
return chart;
10446
};
10447
10448
chart.donutRatio = function(_) {
10449
if (!arguments.length) return donutRatio;
10450
donutRatio = _;
10451
return chart;
10452
};
10453
10454
chart.startAngle = function(_) {
10455
if (!arguments.length) return startAngle;
10456
startAngle = _;
10457
return chart;
10458
};
10459
10460
chart.endAngle = function(_) {
10461
if (!arguments.length) return endAngle;
10462
endAngle = _;
10463
return chart;
10464
};
10465
10466
chart.id = function(_) {
10467
if (!arguments.length) return id;
10468
id = _;
10469
return chart;
10470
};
10471
10472
chart.color = function(_) {
10473
if (!arguments.length) return color;
10474
color = nv.utils.getColor(_);
10475
return chart;
10476
};
10477
10478
chart.valueFormat = function(_) {
10479
if (!arguments.length) return valueFormat;
10480
valueFormat = _;
10481
return chart;
10482
};
10483
10484
chart.labelThreshold = function(_) {
10485
if (!arguments.length) return labelThreshold;
10486
labelThreshold = _;
10487
return chart;
10488
};
10489
//============================================================
10490
10491
10492
return chart;
10493
}
10494
nv.models.pieChart = function() {
10495
"use strict";
10496
//============================================================
10497
// Public Variables with Default Settings
10498
//------------------------------------------------------------
10499
10500
var pie = nv.models.pie()
10501
, legend = nv.models.legend()
10502
;
10503
10504
var margin = {top: 30, right: 20, bottom: 20, left: 20}
10505
, width = null
10506
, height = null
10507
, showLegend = true
10508
, color = nv.utils.defaultColor()
10509
, tooltips = true
10510
, tooltip = function(key, y, e, graph) {
10511
return '<h3>' + key + '</h3>' +
10512
'<p>' + y + '</p>'
10513
}
10514
, state = {}
10515
, defaultState = null
10516
, noData = "No Data Available."
10517
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
10518
;
10519
10520
//============================================================
10521
10522
10523
//============================================================
10524
// Private Variables
10525
//------------------------------------------------------------
10526
10527
var showTooltip = function(e, offsetElement) {
10528
var tooltipLabel = pie.description()(e.point) || pie.x()(e.point)
10529
var left = e.pos[0] + ( (offsetElement && offsetElement.offsetLeft) || 0 ),
10530
top = e.pos[1] + ( (offsetElement && offsetElement.offsetTop) || 0),
10531
y = pie.valueFormat()(pie.y()(e.point)),
10532
content = tooltip(tooltipLabel, y, e, chart);
10533
10534
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
10535
};
10536
10537
//============================================================
10538
10539
10540
function chart(selection) {
10541
selection.each(function(data) {
10542
var container = d3.select(this),
10543
that = this;
10544
10545
var availableWidth = (width || parseInt(container.style('width')) || 960)
10546
- margin.left - margin.right,
10547
availableHeight = (height || parseInt(container.style('height')) || 400)
10548
- margin.top - margin.bottom;
10549
10550
chart.update = function() { container.transition().call(chart); };
10551
chart.container = this;
10552
10553
//set state.disabled
10554
state.disabled = data.map(function(d) { return !!d.disabled });
10555
10556
if (!defaultState) {
10557
var key;
10558
defaultState = {};
10559
for (key in state) {
10560
if (state[key] instanceof Array)
10561
defaultState[key] = state[key].slice(0);
10562
else
10563
defaultState[key] = state[key];
10564
}
10565
}
10566
10567
//------------------------------------------------------------
10568
// Display No Data message if there's nothing to show.
10569
10570
if (!data || !data.length) {
10571
var noDataText = container.selectAll('.nv-noData').data([noData]);
10572
10573
noDataText.enter().append('text')
10574
.attr('class', 'nvd3 nv-noData')
10575
.attr('dy', '-.7em')
10576
.style('text-anchor', 'middle');
10577
10578
noDataText
10579
.attr('x', margin.left + availableWidth / 2)
10580
.attr('y', margin.top + availableHeight / 2)
10581
.text(function(d) { return d });
10582
10583
return chart;
10584
} else {
10585
container.selectAll('.nv-noData').remove();
10586
}
10587
10588
//------------------------------------------------------------
10589
10590
10591
//------------------------------------------------------------
10592
// Setup containers and skeleton of chart
10593
10594
var wrap = container.selectAll('g.nv-wrap.nv-pieChart').data([data]);
10595
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-pieChart').append('g');
10596
var g = wrap.select('g');
10597
10598
gEnter.append('g').attr('class', 'nv-pieWrap');
10599
gEnter.append('g').attr('class', 'nv-legendWrap');
10600
10601
//------------------------------------------------------------
10602
10603
10604
//------------------------------------------------------------
10605
// Legend
10606
10607
if (showLegend) {
10608
legend
10609
.width( availableWidth )
10610
.key(pie.x());
10611
10612
wrap.select('.nv-legendWrap')
10613
.datum(data)
10614
.call(legend);
10615
10616
if ( margin.top != legend.height()) {
10617
margin.top = legend.height();
10618
availableHeight = (height || parseInt(container.style('height')) || 400)
10619
- margin.top - margin.bottom;
10620
}
10621
10622
wrap.select('.nv-legendWrap')
10623
.attr('transform', 'translate(0,' + (-margin.top) +')');
10624
}
10625
10626
//------------------------------------------------------------
10627
10628
10629
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10630
10631
10632
//------------------------------------------------------------
10633
// Main Chart Component(s)
10634
10635
pie
10636
.width(availableWidth)
10637
.height(availableHeight);
10638
10639
10640
var pieWrap = g.select('.nv-pieWrap')
10641
.datum([data]);
10642
10643
d3.transition(pieWrap).call(pie);
10644
10645
//------------------------------------------------------------
10646
10647
10648
//============================================================
10649
// Event Handling/Dispatching (in chart's scope)
10650
//------------------------------------------------------------
10651
10652
legend.dispatch.on('stateChange', function(newState) {
10653
state = newState;
10654
dispatch.stateChange(state);
10655
chart.update();
10656
});
10657
10658
pie.dispatch.on('elementMouseout.tooltip', function(e) {
10659
dispatch.tooltipHide(e);
10660
});
10661
10662
// Update chart from a state object passed to event handler
10663
dispatch.on('changeState', function(e) {
10664
10665
if (typeof e.disabled !== 'undefined') {
10666
data.forEach(function(series,i) {
10667
series.disabled = e.disabled[i];
10668
});
10669
10670
state.disabled = e.disabled;
10671
}
10672
10673
chart.update();
10674
});
10675
10676
//============================================================
10677
10678
10679
});
10680
10681
return chart;
10682
}
10683
10684
//============================================================
10685
// Event Handling/Dispatching (out of chart's scope)
10686
//------------------------------------------------------------
10687
10688
pie.dispatch.on('elementMouseover.tooltip', function(e) {
10689
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
10690
dispatch.tooltipShow(e);
10691
});
10692
10693
dispatch.on('tooltipShow', function(e) {
10694
if (tooltips) showTooltip(e);
10695
});
10696
10697
dispatch.on('tooltipHide', function() {
10698
if (tooltips) nv.tooltip.cleanup();
10699
});
10700
10701
//============================================================
10702
10703
10704
//============================================================
10705
// Expose Public Variables
10706
//------------------------------------------------------------
10707
10708
// expose chart's sub-components
10709
chart.legend = legend;
10710
chart.dispatch = dispatch;
10711
chart.pie = pie;
10712
10713
d3.rebind(chart, pie, 'valueFormat', 'values', 'x', 'y', 'description', 'id', 'showLabels', 'donutLabelsOutside', 'pieLabelsOutside', 'labelType', 'donut', 'donutRatio', 'labelThreshold');
10714
chart.options = nv.utils.optionsFunc.bind(chart);
10715
10716
chart.margin = function(_) {
10717
if (!arguments.length) return margin;
10718
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
10719
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
10720
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
10721
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
10722
return chart;
10723
};
10724
10725
chart.width = function(_) {
10726
if (!arguments.length) return width;
10727
width = _;
10728
return chart;
10729
};
10730
10731
chart.height = function(_) {
10732
if (!arguments.length) return height;
10733
height = _;
10734
return chart;
10735
};
10736
10737
chart.color = function(_) {
10738
if (!arguments.length) return color;
10739
color = nv.utils.getColor(_);
10740
legend.color(color);
10741
pie.color(color);
10742
return chart;
10743
};
10744
10745
chart.showLegend = function(_) {
10746
if (!arguments.length) return showLegend;
10747
showLegend = _;
10748
return chart;
10749
};
10750
10751
chart.tooltips = function(_) {
10752
if (!arguments.length) return tooltips;
10753
tooltips = _;
10754
return chart;
10755
};
10756
10757
chart.tooltipContent = function(_) {
10758
if (!arguments.length) return tooltip;
10759
tooltip = _;
10760
return chart;
10761
};
10762
10763
chart.state = function(_) {
10764
if (!arguments.length) return state;
10765
state = _;
10766
return chart;
10767
};
10768
10769
chart.defaultState = function(_) {
10770
if (!arguments.length) return defaultState;
10771
defaultState = _;
10772
return chart;
10773
};
10774
10775
chart.noData = function(_) {
10776
if (!arguments.length) return noData;
10777
noData = _;
10778
return chart;
10779
};
10780
10781
//============================================================
10782
10783
10784
return chart;
10785
}
10786
10787
nv.models.scatter = function() {
10788
"use strict";
10789
//============================================================
10790
// Public Variables with Default Settings
10791
//------------------------------------------------------------
10792
10793
var margin = {top: 0, right: 0, bottom: 0, left: 0}
10794
, width = 960
10795
, height = 500
10796
, color = nv.utils.defaultColor() // chooses color
10797
, id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't select one
10798
, x = d3.scale.linear()
10799
, y = d3.scale.linear()
10800
, z = d3.scale.linear() //linear because d3.svg.shape.size is treated as area
10801
, getX = function(d) { return d.x } // accessor to get the x value
10802
, getY = function(d) { return d.y } // accessor to get the y value
10803
, getSize = function(d) { return d.size || 1} // accessor to get the point size
10804
, getShape = function(d) { return d.shape || 'circle' } // accessor to get point shape
10805
, onlyCircles = true // Set to false to use shapes
10806
, forceX = [] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
10807
, forceY = [] // List of numbers to Force into the Y scale
10808
, forceSize = [] // List of numbers to Force into the Size scale
10809
, interactive = true // If true, plots a voronoi overlay for advanced point intersection
10810
, pointKey = null
10811
, pointActive = function(d) { return !d.notActive } // any points that return false will be filtered out
10812
, padData = false // If true, adds half a data points width to front and back, for lining up a line chart with a bar chart
10813
, padDataOuter = .1 //outerPadding to imitate ordinal scale outer padding
10814
, clipEdge = false // if true, masks points within x and y scale
10815
, clipVoronoi = true // if true, masks each point with a circle... can turn off to slightly increase performance
10816
, clipRadius = function() { return 25 } // function to get the radius for voronoi point clips
10817
, xDomain = null // Override x domain (skips the calculation from data)
10818
, yDomain = null // Override y domain
10819
, xRange = null // Override x range
10820
, yRange = null // Override y range
10821
, sizeDomain = null // Override point size domain
10822
, sizeRange = null
10823
, singlePoint = false
10824
, dispatch = d3.dispatch('elementClick', 'elementMouseover', 'elementMouseout')
10825
, useVoronoi = true
10826
;
10827
10828
//============================================================
10829
10830
10831
//============================================================
10832
// Private Variables
10833
//------------------------------------------------------------
10834
10835
var x0, y0, z0 // used to store previous scales
10836
, timeoutID
10837
, needsUpdate = false // Flag for when the points are visually updating, but the interactive layer is behind, to disable tooltips
10838
;
10839
10840
//============================================================
10841
10842
10843
function chart(selection) {
10844
selection.each(function(data) {
10845
var availableWidth = width - margin.left - margin.right,
10846
availableHeight = height - margin.top - margin.bottom,
10847
container = d3.select(this);
10848
10849
//add series index to each data point for reference
10850
data = data.map(function(series, i) {
10851
series.values = series.values.map(function(point) {
10852
point.series = i;
10853
return point;
10854
});
10855
return series;
10856
});
10857
10858
//------------------------------------------------------------
10859
// Setup Scales
10860
10861
// remap and flatten the data for use in calculating the scales' domains
10862
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
10863
d3.merge(
10864
data.map(function(d) {
10865
return d.values.map(function(d,i) {
10866
return { x: getX(d,i), y: getY(d,i), size: getSize(d,i) }
10867
})
10868
})
10869
);
10870
10871
x .domain(xDomain || d3.extent(seriesData.map(function(d) { return d.x; }).concat(forceX)))
10872
10873
if (padData && data[0])
10874
x.range(xRange || [(availableWidth * padDataOuter + availableWidth) / (2 *data[0].values.length), availableWidth - availableWidth * (1 + padDataOuter) / (2 * data[0].values.length) ]);
10875
//x.range([availableWidth * .5 / data[0].values.length, availableWidth * (data[0].values.length - .5) / data[0].values.length ]);
10876
else
10877
x.range(xRange || [0, availableWidth]);
10878
10879
y .domain(yDomain || d3.extent(seriesData.map(function(d) { return d.y }).concat(forceY)))
10880
.range(yRange || [availableHeight, 0]);
10881
10882
z .domain(sizeDomain || d3.extent(seriesData.map(function(d) { return d.size }).concat(forceSize)))
10883
.range(sizeRange || [16, 256]);
10884
10885
// If scale's domain don't have a range, slightly adjust to make one... so a chart can show a single data point
10886
if (x.domain()[0] === x.domain()[1] || y.domain()[0] === y.domain()[1]) singlePoint = true;
10887
if (x.domain()[0] === x.domain()[1])
10888
x.domain()[0] ?
10889
x.domain([x.domain()[0] - x.domain()[0] * 0.01, x.domain()[1] + x.domain()[1] * 0.01])
10890
: x.domain([-1,1]);
10891
10892
if (y.domain()[0] === y.domain()[1])
10893
y.domain()[0] ?
10894
y.domain([y.domain()[0] - y.domain()[0] * 0.01, y.domain()[1] + y.domain()[1] * 0.01])
10895
: y.domain([-1,1]);
10896
10897
if ( isNaN(x.domain()[0])) {
10898
x.domain([-1,1]);
10899
}
10900
10901
if ( isNaN(y.domain()[0])) {
10902
y.domain([-1,1]);
10903
}
10904
10905
10906
x0 = x0 || x;
10907
y0 = y0 || y;
10908
z0 = z0 || z;
10909
10910
//------------------------------------------------------------
10911
10912
10913
//------------------------------------------------------------
10914
// Setup containers and skeleton of chart
10915
10916
var wrap = container.selectAll('g.nv-wrap.nv-scatter').data([data]);
10917
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatter nv-chart-' + id + (singlePoint ? ' nv-single-point' : ''));
10918
var defsEnter = wrapEnter.append('defs');
10919
var gEnter = wrapEnter.append('g');
10920
var g = wrap.select('g');
10921
10922
gEnter.append('g').attr('class', 'nv-groups');
10923
gEnter.append('g').attr('class', 'nv-point-paths');
10924
10925
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
10926
10927
//------------------------------------------------------------
10928
10929
10930
defsEnter.append('clipPath')
10931
.attr('id', 'nv-edge-clip-' + id)
10932
.append('rect');
10933
10934
wrap.select('#nv-edge-clip-' + id + ' rect')
10935
.attr('width', availableWidth)
10936
.attr('height', availableHeight);
10937
10938
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
10939
10940
10941
function updateInteractiveLayer() {
10942
10943
if (!interactive) return false;
10944
10945
var eventElements;
10946
10947
var vertices = d3.merge(data.map(function(group, groupIndex) {
10948
return group.values
10949
.map(function(point, pointIndex) {
10950
// *Adding noise to make duplicates very unlikely
10951
// *Injecting series and point index for reference
10952
/* *Adding a 'jitter' to the points, because there's an issue in d3.geom.voronoi.
10953
*/
10954
var pX = getX(point,pointIndex);
10955
var pY = getY(point,pointIndex);
10956
10957
return [x(pX)+ Math.random() * 1e-7,
10958
y(pY)+ Math.random() * 1e-7,
10959
groupIndex,
10960
pointIndex, point]; //temp hack to add noise untill I think of a better way so there are no duplicates
10961
})
10962
.filter(function(pointArray, pointIndex) {
10963
return pointActive(pointArray[4], pointIndex); // Issue #237.. move filter to after map, so pointIndex is correct!
10964
})
10965
})
10966
);
10967
10968
10969
10970
//inject series and point index for reference into voronoi
10971
if (useVoronoi === true) {
10972
10973
if (clipVoronoi) {
10974
var pointClipsEnter = wrap.select('defs').selectAll('.nv-point-clips')
10975
.data([id])
10976
.enter();
10977
10978
pointClipsEnter.append('clipPath')
10979
.attr('class', 'nv-point-clips')
10980
.attr('id', 'nv-points-clip-' + id);
10981
10982
var pointClips = wrap.select('#nv-points-clip-' + id).selectAll('circle')
10983
.data(vertices);
10984
pointClips.enter().append('circle')
10985
.attr('r', clipRadius);
10986
pointClips.exit().remove();
10987
pointClips
10988
.attr('cx', function(d) { return d[0] })
10989
.attr('cy', function(d) { return d[1] });
10990
10991
wrap.select('.nv-point-paths')
10992
.attr('clip-path', 'url(#nv-points-clip-' + id + ')');
10993
}
10994
10995
10996
if(vertices.length) {
10997
// Issue #283 - Adding 2 dummy points to the voronoi b/c voronoi requires min 3 points to work
10998
vertices.push([x.range()[0] - 20, y.range()[0] - 20, null, null]);
10999
vertices.push([x.range()[1] + 20, y.range()[1] + 20, null, null]);
11000
vertices.push([x.range()[0] - 20, y.range()[0] + 20, null, null]);
11001
vertices.push([x.range()[1] + 20, y.range()[1] - 20, null, null]);
11002
}
11003
11004
var bounds = d3.geom.polygon([
11005
[-10,-10],
11006
[-10,height + 10],
11007
[width + 10,height + 10],
11008
[width + 10,-10]
11009
]);
11010
11011
var voronoi = d3.geom.voronoi(vertices).map(function(d, i) {
11012
return {
11013
'data': bounds.clip(d),
11014
'series': vertices[i][2],
11015
'point': vertices[i][3]
11016
}
11017
});
11018
11019
11020
var pointPaths = wrap.select('.nv-point-paths').selectAll('path')
11021
.data(voronoi);
11022
pointPaths.enter().append('path')
11023
.attr('class', function(d,i) { return 'nv-path-'+i; });
11024
pointPaths.exit().remove();
11025
pointPaths
11026
.attr('d', function(d) {
11027
if (d.data.length === 0)
11028
return 'M 0 0'
11029
else
11030
return 'M' + d.data.join('L') + 'Z';
11031
});
11032
11033
var mouseEventCallback = function(d,mDispatch) {
11034
if (needsUpdate) return 0;
11035
var series = data[d.series];
11036
if (typeof series === 'undefined') return;
11037
11038
var point = series.values[d.point];
11039
11040
mDispatch({
11041
point: point,
11042
series: series,
11043
pos: [x(getX(point, d.point)) + margin.left, y(getY(point, d.point)) + margin.top],
11044
seriesIndex: d.series,
11045
pointIndex: d.point
11046
});
11047
};
11048
11049
pointPaths
11050
.on('click', function(d) {
11051
mouseEventCallback(d, dispatch.elementClick);
11052
})
11053
.on('mouseover', function(d) {
11054
mouseEventCallback(d, dispatch.elementMouseover);
11055
})
11056
.on('mouseout', function(d, i) {
11057
mouseEventCallback(d, dispatch.elementMouseout);
11058
});
11059
11060
11061
} else {
11062
/*
11063
// bring data in form needed for click handlers
11064
var dataWithPoints = vertices.map(function(d, i) {
11065
return {
11066
'data': d,
11067
'series': vertices[i][2],
11068
'point': vertices[i][3]
11069
}
11070
});
11071
*/
11072
11073
// add event handlers to points instead voronoi paths
11074
wrap.select('.nv-groups').selectAll('.nv-group')
11075
.selectAll('.nv-point')
11076
//.data(dataWithPoints)
11077
//.style('pointer-events', 'auto') // recativate events, disabled by css
11078
.on('click', function(d,i) {
11079
//nv.log('test', d, i);
11080
if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11081
var series = data[d.series],
11082
point = series.values[i];
11083
11084
dispatch.elementClick({
11085
point: point,
11086
series: series,
11087
pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11088
seriesIndex: d.series,
11089
pointIndex: i
11090
});
11091
})
11092
.on('mouseover', function(d,i) {
11093
if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11094
var series = data[d.series],
11095
point = series.values[i];
11096
11097
dispatch.elementMouseover({
11098
point: point,
11099
series: series,
11100
pos: [x(getX(point, i)) + margin.left, y(getY(point, i)) + margin.top],
11101
seriesIndex: d.series,
11102
pointIndex: i
11103
});
11104
})
11105
.on('mouseout', function(d,i) {
11106
if (needsUpdate || !data[d.series]) return 0; //check if this is a dummy point
11107
var series = data[d.series],
11108
point = series.values[i];
11109
11110
dispatch.elementMouseout({
11111
point: point,
11112
series: series,
11113
seriesIndex: d.series,
11114
pointIndex: i
11115
});
11116
});
11117
}
11118
11119
needsUpdate = false;
11120
}
11121
11122
needsUpdate = true;
11123
11124
var groups = wrap.select('.nv-groups').selectAll('.nv-group')
11125
.data(function(d) { return d }, function(d) { return d.key });
11126
groups.enter().append('g')
11127
.style('stroke-opacity', 1e-6)
11128
.style('fill-opacity', 1e-6);
11129
groups.exit()
11130
.remove();
11131
groups
11132
.attr('class', function(d,i) { return 'nv-group nv-series-' + i })
11133
.classed('hover', function(d) { return d.hover });
11134
groups
11135
.transition()
11136
.style('fill', function(d,i) { return color(d, i) })
11137
.style('stroke', function(d,i) { return color(d, i) })
11138
.style('stroke-opacity', 1)
11139
.style('fill-opacity', .5);
11140
11141
11142
if (onlyCircles) {
11143
11144
var points = groups.selectAll('circle.nv-point')
11145
.data(function(d) { return d.values }, pointKey);
11146
points.enter().append('circle')
11147
.style('fill', function (d,i) { return d.color })
11148
.style('stroke', function (d,i) { return d.color })
11149
.attr('cx', function(d,i) { return nv.utils.NaNtoZero(x0(getX(d,i))) })
11150
.attr('cy', function(d,i) { return nv.utils.NaNtoZero(y0(getY(d,i))) })
11151
.attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11152
points.exit().remove();
11153
groups.exit().selectAll('path.nv-point').transition()
11154
.attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11155
.attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11156
.remove();
11157
points.each(function(d,i) {
11158
d3.select(this)
11159
.classed('nv-point', true)
11160
.classed('nv-point-' + i, true)
11161
.classed('hover',false)
11162
;
11163
});
11164
points.transition()
11165
.attr('cx', function(d,i) { return nv.utils.NaNtoZero(x(getX(d,i))) })
11166
.attr('cy', function(d,i) { return nv.utils.NaNtoZero(y(getY(d,i))) })
11167
.attr('r', function(d,i) { return Math.sqrt(z(getSize(d,i))/Math.PI) });
11168
11169
} else {
11170
11171
var points = groups.selectAll('path.nv-point')
11172
.data(function(d) { return d.values });
11173
points.enter().append('path')
11174
.style('fill', function (d,i) { return d.color })
11175
.style('stroke', function (d,i) { return d.color })
11176
.attr('transform', function(d,i) {
11177
return 'translate(' + x0(getX(d,i)) + ',' + y0(getY(d,i)) + ')'
11178
})
11179
.attr('d',
11180
d3.svg.symbol()
11181
.type(getShape)
11182
.size(function(d,i) { return z(getSize(d,i)) })
11183
);
11184
points.exit().remove();
11185
groups.exit().selectAll('path.nv-point')
11186
.transition()
11187
.attr('transform', function(d,i) {
11188
return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11189
})
11190
.remove();
11191
points.each(function(d,i) {
11192
d3.select(this)
11193
.classed('nv-point', true)
11194
.classed('nv-point-' + i, true)
11195
.classed('hover',false)
11196
;
11197
});
11198
points.transition()
11199
.attr('transform', function(d,i) {
11200
//nv.log(d,i,getX(d,i), x(getX(d,i)));
11201
return 'translate(' + x(getX(d,i)) + ',' + y(getY(d,i)) + ')'
11202
})
11203
.attr('d',
11204
d3.svg.symbol()
11205
.type(getShape)
11206
.size(function(d,i) { return z(getSize(d,i)) })
11207
);
11208
}
11209
11210
11211
// Delay updating the invisible interactive layer for smoother animation
11212
clearTimeout(timeoutID); // stop repeat calls to updateInteractiveLayer
11213
timeoutID = setTimeout(updateInteractiveLayer, 300);
11214
//updateInteractiveLayer();
11215
11216
//store old scales for use in transitions on update
11217
x0 = x.copy();
11218
y0 = y.copy();
11219
z0 = z.copy();
11220
11221
});
11222
11223
return chart;
11224
}
11225
11226
11227
//============================================================
11228
// Event Handling/Dispatching (out of chart's scope)
11229
//------------------------------------------------------------
11230
chart.clearHighlights = function() {
11231
//Remove the 'hover' class from all highlighted points.
11232
d3.selectAll(".nv-chart-" + id + " .nv-point.hover").classed("hover",false);
11233
};
11234
11235
chart.highlightPoint = function(seriesIndex,pointIndex,isHoverOver) {
11236
d3.select(".nv-chart-" + id + " .nv-series-" + seriesIndex + " .nv-point-" + pointIndex)
11237
.classed("hover",isHoverOver);
11238
};
11239
11240
11241
dispatch.on('elementMouseover.point', function(d) {
11242
if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,true);
11243
});
11244
11245
dispatch.on('elementMouseout.point', function(d) {
11246
if (interactive) chart.highlightPoint(d.seriesIndex,d.pointIndex,false);
11247
});
11248
11249
//============================================================
11250
11251
11252
//============================================================
11253
// Expose Public Variables
11254
//------------------------------------------------------------
11255
11256
chart.dispatch = dispatch;
11257
chart.options = nv.utils.optionsFunc.bind(chart);
11258
11259
chart.x = function(_) {
11260
if (!arguments.length) return getX;
11261
getX = d3.functor(_);
11262
return chart;
11263
};
11264
11265
chart.y = function(_) {
11266
if (!arguments.length) return getY;
11267
getY = d3.functor(_);
11268
return chart;
11269
};
11270
11271
chart.size = function(_) {
11272
if (!arguments.length) return getSize;
11273
getSize = d3.functor(_);
11274
return chart;
11275
};
11276
11277
chart.margin = function(_) {
11278
if (!arguments.length) return margin;
11279
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11280
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11281
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11282
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11283
return chart;
11284
};
11285
11286
chart.width = function(_) {
11287
if (!arguments.length) return width;
11288
width = _;
11289
return chart;
11290
};
11291
11292
chart.height = function(_) {
11293
if (!arguments.length) return height;
11294
height = _;
11295
return chart;
11296
};
11297
11298
chart.xScale = function(_) {
11299
if (!arguments.length) return x;
11300
x = _;
11301
return chart;
11302
};
11303
11304
chart.yScale = function(_) {
11305
if (!arguments.length) return y;
11306
y = _;
11307
return chart;
11308
};
11309
11310
chart.zScale = function(_) {
11311
if (!arguments.length) return z;
11312
z = _;
11313
return chart;
11314
};
11315
11316
chart.xDomain = function(_) {
11317
if (!arguments.length) return xDomain;
11318
xDomain = _;
11319
return chart;
11320
};
11321
11322
chart.yDomain = function(_) {
11323
if (!arguments.length) return yDomain;
11324
yDomain = _;
11325
return chart;
11326
};
11327
11328
chart.sizeDomain = function(_) {
11329
if (!arguments.length) return sizeDomain;
11330
sizeDomain = _;
11331
return chart;
11332
};
11333
11334
chart.xRange = function(_) {
11335
if (!arguments.length) return xRange;
11336
xRange = _;
11337
return chart;
11338
};
11339
11340
chart.yRange = function(_) {
11341
if (!arguments.length) return yRange;
11342
yRange = _;
11343
return chart;
11344
};
11345
11346
chart.sizeRange = function(_) {
11347
if (!arguments.length) return sizeRange;
11348
sizeRange = _;
11349
return chart;
11350
};
11351
11352
chart.forceX = function(_) {
11353
if (!arguments.length) return forceX;
11354
forceX = _;
11355
return chart;
11356
};
11357
11358
chart.forceY = function(_) {
11359
if (!arguments.length) return forceY;
11360
forceY = _;
11361
return chart;
11362
};
11363
11364
chart.forceSize = function(_) {
11365
if (!arguments.length) return forceSize;
11366
forceSize = _;
11367
return chart;
11368
};
11369
11370
chart.interactive = function(_) {
11371
if (!arguments.length) return interactive;
11372
interactive = _;
11373
return chart;
11374
};
11375
11376
chart.pointKey = function(_) {
11377
if (!arguments.length) return pointKey;
11378
pointKey = _;
11379
return chart;
11380
};
11381
11382
chart.pointActive = function(_) {
11383
if (!arguments.length) return pointActive;
11384
pointActive = _;
11385
return chart;
11386
};
11387
11388
chart.padData = function(_) {
11389
if (!arguments.length) return padData;
11390
padData = _;
11391
return chart;
11392
};
11393
11394
chart.padDataOuter = function(_) {
11395
if (!arguments.length) return padDataOuter;
11396
padDataOuter = _;
11397
return chart;
11398
};
11399
11400
chart.clipEdge = function(_) {
11401
if (!arguments.length) return clipEdge;
11402
clipEdge = _;
11403
return chart;
11404
};
11405
11406
chart.clipVoronoi= function(_) {
11407
if (!arguments.length) return clipVoronoi;
11408
clipVoronoi = _;
11409
return chart;
11410
};
11411
11412
chart.useVoronoi= function(_) {
11413
if (!arguments.length) return useVoronoi;
11414
useVoronoi = _;
11415
if (useVoronoi === false) {
11416
clipVoronoi = false;
11417
}
11418
return chart;
11419
};
11420
11421
chart.clipRadius = function(_) {
11422
if (!arguments.length) return clipRadius;
11423
clipRadius = _;
11424
return chart;
11425
};
11426
11427
chart.color = function(_) {
11428
if (!arguments.length) return color;
11429
color = nv.utils.getColor(_);
11430
return chart;
11431
};
11432
11433
chart.shape = function(_) {
11434
if (!arguments.length) return getShape;
11435
getShape = _;
11436
return chart;
11437
};
11438
11439
chart.onlyCircles = function(_) {
11440
if (!arguments.length) return onlyCircles;
11441
onlyCircles = _;
11442
return chart;
11443
};
11444
11445
chart.id = function(_) {
11446
if (!arguments.length) return id;
11447
id = _;
11448
return chart;
11449
};
11450
11451
chart.singlePoint = function(_) {
11452
if (!arguments.length) return singlePoint;
11453
singlePoint = _;
11454
return chart;
11455
};
11456
11457
//============================================================
11458
11459
11460
return chart;
11461
}
11462
nv.models.scatterChart = function() {
11463
"use strict";
11464
//============================================================
11465
// Public Variables with Default Settings
11466
//------------------------------------------------------------
11467
11468
var scatter = nv.models.scatter()
11469
, xAxis = nv.models.axis()
11470
, yAxis = nv.models.axis()
11471
, legend = nv.models.legend()
11472
, controls = nv.models.legend()
11473
, distX = nv.models.distribution()
11474
, distY = nv.models.distribution()
11475
;
11476
11477
var margin = {top: 30, right: 20, bottom: 50, left: 75}
11478
, width = null
11479
, height = null
11480
, color = nv.utils.defaultColor()
11481
, x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
11482
, y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
11483
, xPadding = 0
11484
, yPadding = 0
11485
, showDistX = false
11486
, showDistY = false
11487
, showLegend = true
11488
, showXAxis = true
11489
, showYAxis = true
11490
, rightAlignYAxis = false
11491
, showControls = !!d3.fisheye
11492
, fisheye = 0
11493
, pauseFisheye = false
11494
, tooltips = true
11495
, tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
11496
, tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
11497
, tooltip = null
11498
, state = {}
11499
, defaultState = null
11500
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
11501
, noData = "No Data Available."
11502
, transitionDuration = 250
11503
;
11504
11505
scatter
11506
.xScale(x)
11507
.yScale(y)
11508
;
11509
xAxis
11510
.orient('bottom')
11511
.tickPadding(10)
11512
;
11513
yAxis
11514
.orient((rightAlignYAxis) ? 'right' : 'left')
11515
.tickPadding(10)
11516
;
11517
distX
11518
.axis('x')
11519
;
11520
distY
11521
.axis('y')
11522
;
11523
11524
controls.updateState(false);
11525
11526
//============================================================
11527
11528
11529
//============================================================
11530
// Private Variables
11531
//------------------------------------------------------------
11532
11533
var x0, y0;
11534
11535
var showTooltip = function(e, offsetElement) {
11536
//TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
11537
11538
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11539
top = e.pos[1] + ( offsetElement.offsetTop || 0),
11540
leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
11541
topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
11542
leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
11543
topY = e.pos[1] + ( offsetElement.offsetTop || 0),
11544
xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
11545
yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
11546
11547
if( tooltipX != null )
11548
nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
11549
if( tooltipY != null )
11550
nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
11551
if( tooltip != null )
11552
nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
11553
};
11554
11555
var controlsData = [
11556
{ key: 'Magnify', disabled: true }
11557
];
11558
11559
//============================================================
11560
11561
11562
function chart(selection) {
11563
selection.each(function(data) {
11564
var container = d3.select(this),
11565
that = this;
11566
11567
var availableWidth = (width || parseInt(container.style('width')) || 960)
11568
- margin.left - margin.right,
11569
availableHeight = (height || parseInt(container.style('height')) || 400)
11570
- margin.top - margin.bottom;
11571
11572
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
11573
chart.container = this;
11574
11575
//set state.disabled
11576
state.disabled = data.map(function(d) { return !!d.disabled });
11577
11578
if (!defaultState) {
11579
var key;
11580
defaultState = {};
11581
for (key in state) {
11582
if (state[key] instanceof Array)
11583
defaultState[key] = state[key].slice(0);
11584
else
11585
defaultState[key] = state[key];
11586
}
11587
}
11588
11589
//------------------------------------------------------------
11590
// Display noData message if there's nothing to show.
11591
11592
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
11593
var noDataText = container.selectAll('.nv-noData').data([noData]);
11594
11595
noDataText.enter().append('text')
11596
.attr('class', 'nvd3 nv-noData')
11597
.attr('dy', '-.7em')
11598
.style('text-anchor', 'middle');
11599
11600
noDataText
11601
.attr('x', margin.left + availableWidth / 2)
11602
.attr('y', margin.top + availableHeight / 2)
11603
.text(function(d) { return d });
11604
11605
return chart;
11606
} else {
11607
container.selectAll('.nv-noData').remove();
11608
}
11609
11610
//------------------------------------------------------------
11611
11612
11613
//------------------------------------------------------------
11614
// Setup Scales
11615
11616
x0 = x0 || x;
11617
y0 = y0 || y;
11618
11619
//------------------------------------------------------------
11620
11621
11622
//------------------------------------------------------------
11623
// Setup containers and skeleton of chart
11624
11625
var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
11626
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
11627
var gEnter = wrapEnter.append('g');
11628
var g = wrap.select('g');
11629
11630
// background for pointer events
11631
gEnter.append('rect').attr('class', 'nvd3 nv-background');
11632
11633
gEnter.append('g').attr('class', 'nv-x nv-axis');
11634
gEnter.append('g').attr('class', 'nv-y nv-axis');
11635
gEnter.append('g').attr('class', 'nv-scatterWrap');
11636
gEnter.append('g').attr('class', 'nv-distWrap');
11637
gEnter.append('g').attr('class', 'nv-legendWrap');
11638
gEnter.append('g').attr('class', 'nv-controlsWrap');
11639
11640
//------------------------------------------------------------
11641
11642
11643
//------------------------------------------------------------
11644
// Legend
11645
11646
if (showLegend) {
11647
var legendWidth = (showControls) ? availableWidth / 2 : availableWidth;
11648
legend.width(legendWidth);
11649
11650
wrap.select('.nv-legendWrap')
11651
.datum(data)
11652
.call(legend);
11653
11654
if ( margin.top != legend.height()) {
11655
margin.top = legend.height();
11656
availableHeight = (height || parseInt(container.style('height')) || 400)
11657
- margin.top - margin.bottom;
11658
}
11659
11660
wrap.select('.nv-legendWrap')
11661
.attr('transform', 'translate(' + (availableWidth - legendWidth) + ',' + (-margin.top) +')');
11662
}
11663
11664
//------------------------------------------------------------
11665
11666
11667
//------------------------------------------------------------
11668
// Controls
11669
11670
if (showControls) {
11671
controls.width(180).color(['#444']);
11672
g.select('.nv-controlsWrap')
11673
.datum(controlsData)
11674
.attr('transform', 'translate(0,' + (-margin.top) +')')
11675
.call(controls);
11676
}
11677
11678
//------------------------------------------------------------
11679
11680
11681
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
11682
11683
if (rightAlignYAxis) {
11684
g.select(".nv-y.nv-axis")
11685
.attr("transform", "translate(" + availableWidth + ",0)");
11686
}
11687
11688
//------------------------------------------------------------
11689
// Main Chart Component(s)
11690
11691
scatter
11692
.width(availableWidth)
11693
.height(availableHeight)
11694
.color(data.map(function(d,i) {
11695
return d.color || color(d, i);
11696
}).filter(function(d,i) { return !data[i].disabled }));
11697
11698
if (xPadding !== 0)
11699
scatter.xDomain(null);
11700
11701
if (yPadding !== 0)
11702
scatter.yDomain(null);
11703
11704
wrap.select('.nv-scatterWrap')
11705
.datum(data.filter(function(d) { return !d.disabled }))
11706
.call(scatter);
11707
11708
//Adjust for x and y padding
11709
if (xPadding !== 0) {
11710
var xRange = x.domain()[1] - x.domain()[0];
11711
scatter.xDomain([x.domain()[0] - (xPadding * xRange), x.domain()[1] + (xPadding * xRange)]);
11712
}
11713
11714
if (yPadding !== 0) {
11715
var yRange = y.domain()[1] - y.domain()[0];
11716
scatter.yDomain([y.domain()[0] - (yPadding * yRange), y.domain()[1] + (yPadding * yRange)]);
11717
}
11718
11719
//Only need to update the scatter again if x/yPadding changed the domain.
11720
if (yPadding !== 0 || xPadding !== 0) {
11721
wrap.select('.nv-scatterWrap')
11722
.datum(data.filter(function(d) { return !d.disabled }))
11723
.call(scatter);
11724
}
11725
11726
//------------------------------------------------------------
11727
11728
11729
//------------------------------------------------------------
11730
// Setup Axes
11731
if (showXAxis) {
11732
xAxis
11733
.scale(x)
11734
.ticks( xAxis.ticks() && xAxis.ticks().length ? xAxis.ticks() : availableWidth / 100 )
11735
.tickSize( -availableHeight , 0);
11736
11737
g.select('.nv-x.nv-axis')
11738
.attr('transform', 'translate(0,' + y.range()[0] + ')')
11739
.call(xAxis);
11740
11741
}
11742
11743
if (showYAxis) {
11744
yAxis
11745
.scale(y)
11746
.ticks( yAxis.ticks() && yAxis.ticks().length ? yAxis.ticks() : availableHeight / 36 )
11747
.tickSize( -availableWidth, 0);
11748
11749
g.select('.nv-y.nv-axis')
11750
.call(yAxis);
11751
}
11752
11753
11754
if (showDistX) {
11755
distX
11756
.getData(scatter.x())
11757
.scale(x)
11758
.width(availableWidth)
11759
.color(data.map(function(d,i) {
11760
return d.color || color(d, i);
11761
}).filter(function(d,i) { return !data[i].disabled }));
11762
gEnter.select('.nv-distWrap').append('g')
11763
.attr('class', 'nv-distributionX');
11764
g.select('.nv-distributionX')
11765
.attr('transform', 'translate(0,' + y.range()[0] + ')')
11766
.datum(data.filter(function(d) { return !d.disabled }))
11767
.call(distX);
11768
}
11769
11770
if (showDistY) {
11771
distY
11772
.getData(scatter.y())
11773
.scale(y)
11774
.width(availableHeight)
11775
.color(data.map(function(d,i) {
11776
return d.color || color(d, i);
11777
}).filter(function(d,i) { return !data[i].disabled }));
11778
gEnter.select('.nv-distWrap').append('g')
11779
.attr('class', 'nv-distributionY');
11780
g.select('.nv-distributionY')
11781
.attr('transform',
11782
'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
11783
.datum(data.filter(function(d) { return !d.disabled }))
11784
.call(distY);
11785
}
11786
11787
//------------------------------------------------------------
11788
11789
11790
11791
11792
if (d3.fisheye) {
11793
g.select('.nv-background')
11794
.attr('width', availableWidth)
11795
.attr('height', availableHeight);
11796
11797
g.select('.nv-background').on('mousemove', updateFisheye);
11798
g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
11799
scatter.dispatch.on('elementClick.freezeFisheye', function() {
11800
pauseFisheye = !pauseFisheye;
11801
});
11802
}
11803
11804
11805
function updateFisheye() {
11806
if (pauseFisheye) {
11807
g.select('.nv-point-paths').style('pointer-events', 'all');
11808
return false;
11809
}
11810
11811
g.select('.nv-point-paths').style('pointer-events', 'none' );
11812
11813
var mouse = d3.mouse(this);
11814
x.distortion(fisheye).focus(mouse[0]);
11815
y.distortion(fisheye).focus(mouse[1]);
11816
11817
g.select('.nv-scatterWrap')
11818
.call(scatter);
11819
11820
if (showXAxis)
11821
g.select('.nv-x.nv-axis').call(xAxis);
11822
11823
if (showYAxis)
11824
g.select('.nv-y.nv-axis').call(yAxis);
11825
11826
g.select('.nv-distributionX')
11827
.datum(data.filter(function(d) { return !d.disabled }))
11828
.call(distX);
11829
g.select('.nv-distributionY')
11830
.datum(data.filter(function(d) { return !d.disabled }))
11831
.call(distY);
11832
}
11833
11834
11835
11836
//============================================================
11837
// Event Handling/Dispatching (in chart's scope)
11838
//------------------------------------------------------------
11839
11840
controls.dispatch.on('legendClick', function(d,i) {
11841
d.disabled = !d.disabled;
11842
11843
fisheye = d.disabled ? 0 : 2.5;
11844
g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
11845
g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
11846
11847
if (d.disabled) {
11848
x.distortion(fisheye).focus(0);
11849
y.distortion(fisheye).focus(0);
11850
11851
g.select('.nv-scatterWrap').call(scatter);
11852
g.select('.nv-x.nv-axis').call(xAxis);
11853
g.select('.nv-y.nv-axis').call(yAxis);
11854
} else {
11855
pauseFisheye = false;
11856
}
11857
11858
chart.update();
11859
});
11860
11861
legend.dispatch.on('stateChange', function(newState) {
11862
state.disabled = newState.disabled;
11863
dispatch.stateChange(state);
11864
chart.update();
11865
});
11866
11867
scatter.dispatch.on('elementMouseover.tooltip', function(e) {
11868
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11869
.attr('y1', function(d,i) { return e.pos[1] - availableHeight;});
11870
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11871
.attr('x2', e.pos[0] + distX.size());
11872
11873
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
11874
dispatch.tooltipShow(e);
11875
});
11876
11877
dispatch.on('tooltipShow', function(e) {
11878
if (tooltips) showTooltip(e, that.parentNode);
11879
});
11880
11881
// Update chart from a state object passed to event handler
11882
dispatch.on('changeState', function(e) {
11883
11884
if (typeof e.disabled !== 'undefined') {
11885
data.forEach(function(series,i) {
11886
series.disabled = e.disabled[i];
11887
});
11888
11889
state.disabled = e.disabled;
11890
}
11891
11892
chart.update();
11893
});
11894
11895
//============================================================
11896
11897
11898
//store old scales for use in transitions on update
11899
x0 = x.copy();
11900
y0 = y.copy();
11901
11902
11903
});
11904
11905
return chart;
11906
}
11907
11908
11909
//============================================================
11910
// Event Handling/Dispatching (out of chart's scope)
11911
//------------------------------------------------------------
11912
11913
scatter.dispatch.on('elementMouseout.tooltip', function(e) {
11914
dispatch.tooltipHide(e);
11915
11916
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
11917
.attr('y1', 0);
11918
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
11919
.attr('x2', distY.size());
11920
});
11921
dispatch.on('tooltipHide', function() {
11922
if (tooltips) nv.tooltip.cleanup();
11923
});
11924
11925
//============================================================
11926
11927
11928
//============================================================
11929
// Expose Public Variables
11930
//------------------------------------------------------------
11931
11932
// expose chart's sub-components
11933
chart.dispatch = dispatch;
11934
chart.scatter = scatter;
11935
chart.legend = legend;
11936
chart.controls = controls;
11937
chart.xAxis = xAxis;
11938
chart.yAxis = yAxis;
11939
chart.distX = distX;
11940
chart.distY = distY;
11941
11942
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');
11943
chart.options = nv.utils.optionsFunc.bind(chart);
11944
11945
chart.margin = function(_) {
11946
if (!arguments.length) return margin;
11947
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
11948
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
11949
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
11950
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
11951
return chart;
11952
};
11953
11954
chart.width = function(_) {
11955
if (!arguments.length) return width;
11956
width = _;
11957
return chart;
11958
};
11959
11960
chart.height = function(_) {
11961
if (!arguments.length) return height;
11962
height = _;
11963
return chart;
11964
};
11965
11966
chart.color = function(_) {
11967
if (!arguments.length) return color;
11968
color = nv.utils.getColor(_);
11969
legend.color(color);
11970
distX.color(color);
11971
distY.color(color);
11972
return chart;
11973
};
11974
11975
chart.showDistX = function(_) {
11976
if (!arguments.length) return showDistX;
11977
showDistX = _;
11978
return chart;
11979
};
11980
11981
chart.showDistY = function(_) {
11982
if (!arguments.length) return showDistY;
11983
showDistY = _;
11984
return chart;
11985
};
11986
11987
chart.showControls = function(_) {
11988
if (!arguments.length) return showControls;
11989
showControls = _;
11990
return chart;
11991
};
11992
11993
chart.showLegend = function(_) {
11994
if (!arguments.length) return showLegend;
11995
showLegend = _;
11996
return chart;
11997
};
11998
11999
chart.showXAxis = function(_) {
12000
if (!arguments.length) return showXAxis;
12001
showXAxis = _;
12002
return chart;
12003
};
12004
12005
chart.showYAxis = function(_) {
12006
if (!arguments.length) return showYAxis;
12007
showYAxis = _;
12008
return chart;
12009
};
12010
12011
chart.rightAlignYAxis = function(_) {
12012
if(!arguments.length) return rightAlignYAxis;
12013
rightAlignYAxis = _;
12014
yAxis.orient( (_) ? 'right' : 'left');
12015
return chart;
12016
};
12017
12018
12019
chart.fisheye = function(_) {
12020
if (!arguments.length) return fisheye;
12021
fisheye = _;
12022
return chart;
12023
};
12024
12025
chart.xPadding = function(_) {
12026
if (!arguments.length) return xPadding;
12027
xPadding = _;
12028
return chart;
12029
};
12030
12031
chart.yPadding = function(_) {
12032
if (!arguments.length) return yPadding;
12033
yPadding = _;
12034
return chart;
12035
};
12036
12037
chart.tooltips = function(_) {
12038
if (!arguments.length) return tooltips;
12039
tooltips = _;
12040
return chart;
12041
};
12042
12043
chart.tooltipContent = function(_) {
12044
if (!arguments.length) return tooltip;
12045
tooltip = _;
12046
return chart;
12047
};
12048
12049
chart.tooltipXContent = function(_) {
12050
if (!arguments.length) return tooltipX;
12051
tooltipX = _;
12052
return chart;
12053
};
12054
12055
chart.tooltipYContent = function(_) {
12056
if (!arguments.length) return tooltipY;
12057
tooltipY = _;
12058
return chart;
12059
};
12060
12061
chart.state = function(_) {
12062
if (!arguments.length) return state;
12063
state = _;
12064
return chart;
12065
};
12066
12067
chart.defaultState = function(_) {
12068
if (!arguments.length) return defaultState;
12069
defaultState = _;
12070
return chart;
12071
};
12072
12073
chart.noData = function(_) {
12074
if (!arguments.length) return noData;
12075
noData = _;
12076
return chart;
12077
};
12078
12079
chart.transitionDuration = function(_) {
12080
if (!arguments.length) return transitionDuration;
12081
transitionDuration = _;
12082
return chart;
12083
};
12084
12085
//============================================================
12086
12087
12088
return chart;
12089
}
12090
12091
nv.models.scatterPlusLineChart = function() {
12092
"use strict";
12093
//============================================================
12094
// Public Variables with Default Settings
12095
//------------------------------------------------------------
12096
12097
var scatter = nv.models.scatter()
12098
, xAxis = nv.models.axis()
12099
, yAxis = nv.models.axis()
12100
, legend = nv.models.legend()
12101
, controls = nv.models.legend()
12102
, distX = nv.models.distribution()
12103
, distY = nv.models.distribution()
12104
;
12105
12106
var margin = {top: 30, right: 20, bottom: 50, left: 75}
12107
, width = null
12108
, height = null
12109
, color = nv.utils.defaultColor()
12110
, x = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.xScale()
12111
, y = d3.fisheye ? d3.fisheye.scale(d3.scale.linear).distortion(0) : scatter.yScale()
12112
, showDistX = false
12113
, showDistY = false
12114
, showLegend = true
12115
, showXAxis = true
12116
, showYAxis = true
12117
, rightAlignYAxis = false
12118
, showControls = !!d3.fisheye
12119
, fisheye = 0
12120
, pauseFisheye = false
12121
, tooltips = true
12122
, tooltipX = function(key, x, y) { return '<strong>' + x + '</strong>' }
12123
, tooltipY = function(key, x, y) { return '<strong>' + y + '</strong>' }
12124
, tooltip = function(key, x, y, date) { return '<h3>' + key + '</h3>'
12125
+ '<p>' + date + '</p>' }
12126
, state = {}
12127
, defaultState = null
12128
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
12129
, noData = "No Data Available."
12130
, transitionDuration = 250
12131
;
12132
12133
scatter
12134
.xScale(x)
12135
.yScale(y)
12136
;
12137
xAxis
12138
.orient('bottom')
12139
.tickPadding(10)
12140
;
12141
yAxis
12142
.orient((rightAlignYAxis) ? 'right' : 'left')
12143
.tickPadding(10)
12144
;
12145
distX
12146
.axis('x')
12147
;
12148
distY
12149
.axis('y')
12150
;
12151
12152
controls.updateState(false);
12153
//============================================================
12154
12155
12156
//============================================================
12157
// Private Variables
12158
//------------------------------------------------------------
12159
12160
var x0, y0;
12161
12162
var showTooltip = function(e, offsetElement) {
12163
//TODO: make tooltip style an option between single or dual on axes (maybe on all charts with axes?)
12164
12165
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12166
top = e.pos[1] + ( offsetElement.offsetTop || 0),
12167
leftX = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
12168
topX = y.range()[0] + margin.top + ( offsetElement.offsetTop || 0),
12169
leftY = x.range()[0] + margin.left + ( offsetElement.offsetLeft || 0 ),
12170
topY = e.pos[1] + ( offsetElement.offsetTop || 0),
12171
xVal = xAxis.tickFormat()(scatter.x()(e.point, e.pointIndex)),
12172
yVal = yAxis.tickFormat()(scatter.y()(e.point, e.pointIndex));
12173
12174
if( tooltipX != null )
12175
nv.tooltip.show([leftX, topX], tooltipX(e.series.key, xVal, yVal, e, chart), 'n', 1, offsetElement, 'x-nvtooltip');
12176
if( tooltipY != null )
12177
nv.tooltip.show([leftY, topY], tooltipY(e.series.key, xVal, yVal, e, chart), 'e', 1, offsetElement, 'y-nvtooltip');
12178
if( tooltip != null )
12179
nv.tooltip.show([left, top], tooltip(e.series.key, xVal, yVal, e.point.tooltip, e, chart), e.value < 0 ? 'n' : 's', null, offsetElement);
12180
};
12181
12182
var controlsData = [
12183
{ key: 'Magnify', disabled: true }
12184
];
12185
12186
//============================================================
12187
12188
12189
function chart(selection) {
12190
selection.each(function(data) {
12191
var container = d3.select(this),
12192
that = this;
12193
12194
var availableWidth = (width || parseInt(container.style('width')) || 960)
12195
- margin.left - margin.right,
12196
availableHeight = (height || parseInt(container.style('height')) || 400)
12197
- margin.top - margin.bottom;
12198
12199
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
12200
chart.container = this;
12201
12202
//set state.disabled
12203
state.disabled = data.map(function(d) { return !!d.disabled });
12204
12205
if (!defaultState) {
12206
var key;
12207
defaultState = {};
12208
for (key in state) {
12209
if (state[key] instanceof Array)
12210
defaultState[key] = state[key].slice(0);
12211
else
12212
defaultState[key] = state[key];
12213
}
12214
}
12215
12216
//------------------------------------------------------------
12217
// Display noData message if there's nothing to show.
12218
12219
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
12220
var noDataText = container.selectAll('.nv-noData').data([noData]);
12221
12222
noDataText.enter().append('text')
12223
.attr('class', 'nvd3 nv-noData')
12224
.attr('dy', '-.7em')
12225
.style('text-anchor', 'middle');
12226
12227
noDataText
12228
.attr('x', margin.left + availableWidth / 2)
12229
.attr('y', margin.top + availableHeight / 2)
12230
.text(function(d) { return d });
12231
12232
return chart;
12233
} else {
12234
container.selectAll('.nv-noData').remove();
12235
}
12236
12237
//------------------------------------------------------------
12238
12239
12240
//------------------------------------------------------------
12241
// Setup Scales
12242
12243
x = scatter.xScale();
12244
y = scatter.yScale();
12245
12246
x0 = x0 || x;
12247
y0 = y0 || y;
12248
12249
//------------------------------------------------------------
12250
12251
12252
//------------------------------------------------------------
12253
// Setup containers and skeleton of chart
12254
12255
var wrap = container.selectAll('g.nv-wrap.nv-scatterChart').data([data]);
12256
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-scatterChart nv-chart-' + scatter.id());
12257
var gEnter = wrapEnter.append('g');
12258
var g = wrap.select('g')
12259
12260
// background for pointer events
12261
gEnter.append('rect').attr('class', 'nvd3 nv-background').style("pointer-events","none");
12262
12263
gEnter.append('g').attr('class', 'nv-x nv-axis');
12264
gEnter.append('g').attr('class', 'nv-y nv-axis');
12265
gEnter.append('g').attr('class', 'nv-scatterWrap');
12266
gEnter.append('g').attr('class', 'nv-regressionLinesWrap');
12267
gEnter.append('g').attr('class', 'nv-distWrap');
12268
gEnter.append('g').attr('class', 'nv-legendWrap');
12269
gEnter.append('g').attr('class', 'nv-controlsWrap');
12270
12271
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12272
12273
if (rightAlignYAxis) {
12274
g.select(".nv-y.nv-axis")
12275
.attr("transform", "translate(" + availableWidth + ",0)");
12276
}
12277
12278
//------------------------------------------------------------
12279
12280
12281
//------------------------------------------------------------
12282
// Legend
12283
12284
if (showLegend) {
12285
legend.width( availableWidth / 2 );
12286
12287
wrap.select('.nv-legendWrap')
12288
.datum(data)
12289
.call(legend);
12290
12291
if ( margin.top != legend.height()) {
12292
margin.top = legend.height();
12293
availableHeight = (height || parseInt(container.style('height')) || 400)
12294
- margin.top - margin.bottom;
12295
}
12296
12297
wrap.select('.nv-legendWrap')
12298
.attr('transform', 'translate(' + (availableWidth / 2) + ',' + (-margin.top) +')');
12299
}
12300
12301
//------------------------------------------------------------
12302
12303
12304
//------------------------------------------------------------
12305
// Controls
12306
12307
if (showControls) {
12308
controls.width(180).color(['#444']);
12309
g.select('.nv-controlsWrap')
12310
.datum(controlsData)
12311
.attr('transform', 'translate(0,' + (-margin.top) +')')
12312
.call(controls);
12313
}
12314
12315
//------------------------------------------------------------
12316
12317
12318
//------------------------------------------------------------
12319
// Main Chart Component(s)
12320
12321
scatter
12322
.width(availableWidth)
12323
.height(availableHeight)
12324
.color(data.map(function(d,i) {
12325
return d.color || color(d, i);
12326
}).filter(function(d,i) { return !data[i].disabled }))
12327
12328
wrap.select('.nv-scatterWrap')
12329
.datum(data.filter(function(d) { return !d.disabled }))
12330
.call(scatter);
12331
12332
wrap.select('.nv-regressionLinesWrap')
12333
.attr('clip-path', 'url(#nv-edge-clip-' + scatter.id() + ')');
12334
12335
var regWrap = wrap.select('.nv-regressionLinesWrap').selectAll('.nv-regLines')
12336
.data(function(d) {return d });
12337
12338
regWrap.enter().append('g').attr('class', 'nv-regLines');
12339
12340
var regLine = regWrap.selectAll('.nv-regLine').data(function(d){return [d]});
12341
var regLineEnter = regLine.enter()
12342
.append('line').attr('class', 'nv-regLine')
12343
.style('stroke-opacity', 0);
12344
12345
regLine
12346
.transition()
12347
.attr('x1', x.range()[0])
12348
.attr('x2', x.range()[1])
12349
.attr('y1', function(d,i) {return y(x.domain()[0] * d.slope + d.intercept) })
12350
.attr('y2', function(d,i) { return y(x.domain()[1] * d.slope + d.intercept) })
12351
.style('stroke', function(d,i,j) { return color(d,j) })
12352
.style('stroke-opacity', function(d,i) {
12353
return (d.disabled || typeof d.slope === 'undefined' || typeof d.intercept === 'undefined') ? 0 : 1
12354
});
12355
12356
//------------------------------------------------------------
12357
12358
12359
//------------------------------------------------------------
12360
// Setup Axes
12361
12362
if (showXAxis) {
12363
xAxis
12364
.scale(x)
12365
.ticks( xAxis.ticks() ? xAxis.ticks() : availableWidth / 100 )
12366
.tickSize( -availableHeight , 0);
12367
12368
g.select('.nv-x.nv-axis')
12369
.attr('transform', 'translate(0,' + y.range()[0] + ')')
12370
.call(xAxis);
12371
}
12372
12373
if (showYAxis) {
12374
yAxis
12375
.scale(y)
12376
.ticks( yAxis.ticks() ? yAxis.ticks() : availableHeight / 36 )
12377
.tickSize( -availableWidth, 0);
12378
12379
g.select('.nv-y.nv-axis')
12380
.call(yAxis);
12381
}
12382
12383
12384
if (showDistX) {
12385
distX
12386
.getData(scatter.x())
12387
.scale(x)
12388
.width(availableWidth)
12389
.color(data.map(function(d,i) {
12390
return d.color || color(d, i);
12391
}).filter(function(d,i) { return !data[i].disabled }));
12392
gEnter.select('.nv-distWrap').append('g')
12393
.attr('class', 'nv-distributionX');
12394
g.select('.nv-distributionX')
12395
.attr('transform', 'translate(0,' + y.range()[0] + ')')
12396
.datum(data.filter(function(d) { return !d.disabled }))
12397
.call(distX);
12398
}
12399
12400
if (showDistY) {
12401
distY
12402
.getData(scatter.y())
12403
.scale(y)
12404
.width(availableHeight)
12405
.color(data.map(function(d,i) {
12406
return d.color || color(d, i);
12407
}).filter(function(d,i) { return !data[i].disabled }));
12408
gEnter.select('.nv-distWrap').append('g')
12409
.attr('class', 'nv-distributionY');
12410
g.select('.nv-distributionY')
12411
.attr('transform', 'translate(' + (rightAlignYAxis ? availableWidth : -distY.size() ) + ',0)')
12412
.datum(data.filter(function(d) { return !d.disabled }))
12413
.call(distY);
12414
}
12415
12416
//------------------------------------------------------------
12417
12418
12419
12420
12421
if (d3.fisheye) {
12422
g.select('.nv-background')
12423
.attr('width', availableWidth)
12424
.attr('height', availableHeight)
12425
;
12426
12427
g.select('.nv-background').on('mousemove', updateFisheye);
12428
g.select('.nv-background').on('click', function() { pauseFisheye = !pauseFisheye;});
12429
scatter.dispatch.on('elementClick.freezeFisheye', function() {
12430
pauseFisheye = !pauseFisheye;
12431
});
12432
}
12433
12434
12435
function updateFisheye() {
12436
if (pauseFisheye) {
12437
g.select('.nv-point-paths').style('pointer-events', 'all');
12438
return false;
12439
}
12440
12441
g.select('.nv-point-paths').style('pointer-events', 'none' );
12442
12443
var mouse = d3.mouse(this);
12444
x.distortion(fisheye).focus(mouse[0]);
12445
y.distortion(fisheye).focus(mouse[1]);
12446
12447
g.select('.nv-scatterWrap')
12448
.datum(data.filter(function(d) { return !d.disabled }))
12449
.call(scatter);
12450
12451
if (showXAxis)
12452
g.select('.nv-x.nv-axis').call(xAxis);
12453
12454
if (showYAxis)
12455
g.select('.nv-y.nv-axis').call(yAxis);
12456
12457
g.select('.nv-distributionX')
12458
.datum(data.filter(function(d) { return !d.disabled }))
12459
.call(distX);
12460
g.select('.nv-distributionY')
12461
.datum(data.filter(function(d) { return !d.disabled }))
12462
.call(distY);
12463
}
12464
12465
12466
12467
//============================================================
12468
// Event Handling/Dispatching (in chart's scope)
12469
//------------------------------------------------------------
12470
12471
controls.dispatch.on('legendClick', function(d,i) {
12472
d.disabled = !d.disabled;
12473
12474
fisheye = d.disabled ? 0 : 2.5;
12475
g.select('.nv-background') .style('pointer-events', d.disabled ? 'none' : 'all');
12476
g.select('.nv-point-paths').style('pointer-events', d.disabled ? 'all' : 'none' );
12477
12478
if (d.disabled) {
12479
x.distortion(fisheye).focus(0);
12480
y.distortion(fisheye).focus(0);
12481
12482
g.select('.nv-scatterWrap').call(scatter);
12483
g.select('.nv-x.nv-axis').call(xAxis);
12484
g.select('.nv-y.nv-axis').call(yAxis);
12485
} else {
12486
pauseFisheye = false;
12487
}
12488
12489
chart.update();
12490
});
12491
12492
legend.dispatch.on('stateChange', function(newState) {
12493
state = newState;
12494
dispatch.stateChange(state);
12495
chart.update();
12496
});
12497
12498
12499
scatter.dispatch.on('elementMouseover.tooltip', function(e) {
12500
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12501
.attr('y1', e.pos[1] - availableHeight);
12502
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12503
.attr('x2', e.pos[0] + distX.size());
12504
12505
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top];
12506
dispatch.tooltipShow(e);
12507
});
12508
12509
dispatch.on('tooltipShow', function(e) {
12510
if (tooltips) showTooltip(e, that.parentNode);
12511
});
12512
12513
// Update chart from a state object passed to event handler
12514
dispatch.on('changeState', function(e) {
12515
12516
if (typeof e.disabled !== 'undefined') {
12517
data.forEach(function(series,i) {
12518
series.disabled = e.disabled[i];
12519
});
12520
12521
state.disabled = e.disabled;
12522
}
12523
12524
chart.update();
12525
});
12526
12527
//============================================================
12528
12529
12530
//store old scales for use in transitions on update
12531
x0 = x.copy();
12532
y0 = y.copy();
12533
12534
12535
});
12536
12537
return chart;
12538
}
12539
12540
12541
//============================================================
12542
// Event Handling/Dispatching (out of chart's scope)
12543
//------------------------------------------------------------
12544
12545
scatter.dispatch.on('elementMouseout.tooltip', function(e) {
12546
dispatch.tooltipHide(e);
12547
12548
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-distx-' + e.pointIndex)
12549
.attr('y1', 0);
12550
d3.select('.nv-chart-' + scatter.id() + ' .nv-series-' + e.seriesIndex + ' .nv-disty-' + e.pointIndex)
12551
.attr('x2', distY.size());
12552
});
12553
dispatch.on('tooltipHide', function() {
12554
if (tooltips) nv.tooltip.cleanup();
12555
});
12556
12557
//============================================================
12558
12559
12560
//============================================================
12561
// Expose Public Variables
12562
//------------------------------------------------------------
12563
12564
// expose chart's sub-components
12565
chart.dispatch = dispatch;
12566
chart.scatter = scatter;
12567
chart.legend = legend;
12568
chart.controls = controls;
12569
chart.xAxis = xAxis;
12570
chart.yAxis = yAxis;
12571
chart.distX = distX;
12572
chart.distY = distY;
12573
12574
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');
12575
12576
chart.options = nv.utils.optionsFunc.bind(chart);
12577
12578
chart.margin = function(_) {
12579
if (!arguments.length) return margin;
12580
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12581
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12582
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12583
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12584
return chart;
12585
};
12586
12587
chart.width = function(_) {
12588
if (!arguments.length) return width;
12589
width = _;
12590
return chart;
12591
};
12592
12593
chart.height = function(_) {
12594
if (!arguments.length) return height;
12595
height = _;
12596
return chart;
12597
};
12598
12599
chart.color = function(_) {
12600
if (!arguments.length) return color;
12601
color = nv.utils.getColor(_);
12602
legend.color(color);
12603
distX.color(color);
12604
distY.color(color);
12605
return chart;
12606
};
12607
12608
chart.showDistX = function(_) {
12609
if (!arguments.length) return showDistX;
12610
showDistX = _;
12611
return chart;
12612
};
12613
12614
chart.showDistY = function(_) {
12615
if (!arguments.length) return showDistY;
12616
showDistY = _;
12617
return chart;
12618
};
12619
12620
chart.showControls = function(_) {
12621
if (!arguments.length) return showControls;
12622
showControls = _;
12623
return chart;
12624
};
12625
12626
chart.showLegend = function(_) {
12627
if (!arguments.length) return showLegend;
12628
showLegend = _;
12629
return chart;
12630
};
12631
12632
chart.showXAxis = function(_) {
12633
if (!arguments.length) return showXAxis;
12634
showXAxis = _;
12635
return chart;
12636
};
12637
12638
chart.showYAxis = function(_) {
12639
if (!arguments.length) return showYAxis;
12640
showYAxis = _;
12641
return chart;
12642
};
12643
12644
chart.rightAlignYAxis = function(_) {
12645
if(!arguments.length) return rightAlignYAxis;
12646
rightAlignYAxis = _;
12647
yAxis.orient( (_) ? 'right' : 'left');
12648
return chart;
12649
};
12650
12651
chart.fisheye = function(_) {
12652
if (!arguments.length) return fisheye;
12653
fisheye = _;
12654
return chart;
12655
};
12656
12657
chart.tooltips = function(_) {
12658
if (!arguments.length) return tooltips;
12659
tooltips = _;
12660
return chart;
12661
};
12662
12663
chart.tooltipContent = function(_) {
12664
if (!arguments.length) return tooltip;
12665
tooltip = _;
12666
return chart;
12667
};
12668
12669
chart.tooltipXContent = function(_) {
12670
if (!arguments.length) return tooltipX;
12671
tooltipX = _;
12672
return chart;
12673
};
12674
12675
chart.tooltipYContent = function(_) {
12676
if (!arguments.length) return tooltipY;
12677
tooltipY = _;
12678
return chart;
12679
};
12680
12681
chart.state = function(_) {
12682
if (!arguments.length) return state;
12683
state = _;
12684
return chart;
12685
};
12686
12687
chart.defaultState = function(_) {
12688
if (!arguments.length) return defaultState;
12689
defaultState = _;
12690
return chart;
12691
};
12692
12693
chart.noData = function(_) {
12694
if (!arguments.length) return noData;
12695
noData = _;
12696
return chart;
12697
};
12698
12699
chart.transitionDuration = function(_) {
12700
if (!arguments.length) return transitionDuration;
12701
transitionDuration = _;
12702
return chart;
12703
};
12704
12705
//============================================================
12706
12707
12708
return chart;
12709
}
12710
12711
nv.models.sparkline = function() {
12712
"use strict";
12713
//============================================================
12714
// Public Variables with Default Settings
12715
//------------------------------------------------------------
12716
12717
var margin = {top: 2, right: 0, bottom: 2, left: 0}
12718
, width = 400
12719
, height = 32
12720
, animate = true
12721
, x = d3.scale.linear()
12722
, y = d3.scale.linear()
12723
, getX = function(d) { return d.x }
12724
, getY = function(d) { return d.y }
12725
, color = nv.utils.getColor(['#000'])
12726
, xDomain
12727
, yDomain
12728
, xRange
12729
, yRange
12730
;
12731
12732
//============================================================
12733
12734
12735
function chart(selection) {
12736
selection.each(function(data) {
12737
var availableWidth = width - margin.left - margin.right,
12738
availableHeight = height - margin.top - margin.bottom,
12739
container = d3.select(this);
12740
12741
12742
//------------------------------------------------------------
12743
// Setup Scales
12744
12745
x .domain(xDomain || d3.extent(data, getX ))
12746
.range(xRange || [0, availableWidth]);
12747
12748
y .domain(yDomain || d3.extent(data, getY ))
12749
.range(yRange || [availableHeight, 0]);
12750
12751
//------------------------------------------------------------
12752
12753
12754
//------------------------------------------------------------
12755
// Setup containers and skeleton of chart
12756
12757
var wrap = container.selectAll('g.nv-wrap.nv-sparkline').data([data]);
12758
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparkline');
12759
var gEnter = wrapEnter.append('g');
12760
var g = wrap.select('g');
12761
12762
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
12763
12764
//------------------------------------------------------------
12765
12766
12767
var paths = wrap.selectAll('path')
12768
.data(function(d) { return [d] });
12769
paths.enter().append('path');
12770
paths.exit().remove();
12771
paths
12772
.style('stroke', function(d,i) { return d.color || color(d, i) })
12773
.attr('d', d3.svg.line()
12774
.x(function(d,i) { return x(getX(d,i)) })
12775
.y(function(d,i) { return y(getY(d,i)) })
12776
);
12777
12778
12779
// TODO: Add CURRENT data point (Need Min, Mac, Current / Most recent)
12780
var points = wrap.selectAll('circle.nv-point')
12781
.data(function(data) {
12782
var yValues = data.map(function(d, i) { return getY(d,i); });
12783
function pointIndex(index) {
12784
if (index != -1) {
12785
var result = data[index];
12786
result.pointIndex = index;
12787
return result;
12788
} else {
12789
return null;
12790
}
12791
}
12792
var maxPoint = pointIndex(yValues.lastIndexOf(y.domain()[1])),
12793
minPoint = pointIndex(yValues.indexOf(y.domain()[0])),
12794
currentPoint = pointIndex(yValues.length - 1);
12795
return [minPoint, maxPoint, currentPoint].filter(function (d) {return d != null;});
12796
});
12797
points.enter().append('circle');
12798
points.exit().remove();
12799
points
12800
.attr('cx', function(d,i) { return x(getX(d,d.pointIndex)) })
12801
.attr('cy', function(d,i) { return y(getY(d,d.pointIndex)) })
12802
.attr('r', 2)
12803
.attr('class', function(d,i) {
12804
return getX(d, d.pointIndex) == x.domain()[1] ? 'nv-point nv-currentValue' :
12805
getY(d, d.pointIndex) == y.domain()[0] ? 'nv-point nv-minValue' : 'nv-point nv-maxValue'
12806
});
12807
});
12808
12809
return chart;
12810
}
12811
12812
12813
//============================================================
12814
// Expose Public Variables
12815
//------------------------------------------------------------
12816
chart.options = nv.utils.optionsFunc.bind(chart);
12817
12818
chart.margin = function(_) {
12819
if (!arguments.length) return margin;
12820
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
12821
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
12822
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
12823
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
12824
return chart;
12825
};
12826
12827
chart.width = function(_) {
12828
if (!arguments.length) return width;
12829
width = _;
12830
return chart;
12831
};
12832
12833
chart.height = function(_) {
12834
if (!arguments.length) return height;
12835
height = _;
12836
return chart;
12837
};
12838
12839
chart.x = function(_) {
12840
if (!arguments.length) return getX;
12841
getX = d3.functor(_);
12842
return chart;
12843
};
12844
12845
chart.y = function(_) {
12846
if (!arguments.length) return getY;
12847
getY = d3.functor(_);
12848
return chart;
12849
};
12850
12851
chart.xScale = function(_) {
12852
if (!arguments.length) return x;
12853
x = _;
12854
return chart;
12855
};
12856
12857
chart.yScale = function(_) {
12858
if (!arguments.length) return y;
12859
y = _;
12860
return chart;
12861
};
12862
12863
chart.xDomain = function(_) {
12864
if (!arguments.length) return xDomain;
12865
xDomain = _;
12866
return chart;
12867
};
12868
12869
chart.yDomain = function(_) {
12870
if (!arguments.length) return yDomain;
12871
yDomain = _;
12872
return chart;
12873
};
12874
12875
chart.xRange = function(_) {
12876
if (!arguments.length) return xRange;
12877
xRange = _;
12878
return chart;
12879
};
12880
12881
chart.yRange = function(_) {
12882
if (!arguments.length) return yRange;
12883
yRange = _;
12884
return chart;
12885
};
12886
12887
chart.animate = function(_) {
12888
if (!arguments.length) return animate;
12889
animate = _;
12890
return chart;
12891
};
12892
12893
chart.color = function(_) {
12894
if (!arguments.length) return color;
12895
color = nv.utils.getColor(_);
12896
return chart;
12897
};
12898
12899
//============================================================
12900
12901
12902
return chart;
12903
}
12904
12905
nv.models.sparklinePlus = function() {
12906
"use strict";
12907
//============================================================
12908
// Public Variables with Default Settings
12909
//------------------------------------------------------------
12910
12911
var sparkline = nv.models.sparkline();
12912
12913
var margin = {top: 15, right: 100, bottom: 10, left: 50}
12914
, width = null
12915
, height = null
12916
, x
12917
, y
12918
, index = []
12919
, paused = false
12920
, xTickFormat = d3.format(',r')
12921
, yTickFormat = d3.format(',.2f')
12922
, showValue = true
12923
, alignValue = true
12924
, rightAlignValue = false
12925
, noData = "No Data Available."
12926
;
12927
12928
//============================================================
12929
12930
12931
function chart(selection) {
12932
selection.each(function(data) {
12933
var container = d3.select(this);
12934
12935
var availableWidth = (width || parseInt(container.style('width')) || 960)
12936
- margin.left - margin.right,
12937
availableHeight = (height || parseInt(container.style('height')) || 400)
12938
- margin.top - margin.bottom;
12939
12940
12941
12942
chart.update = function() { chart(selection) };
12943
chart.container = this;
12944
12945
12946
//------------------------------------------------------------
12947
// Display No Data message if there's nothing to show.
12948
12949
if (!data || !data.length) {
12950
var noDataText = container.selectAll('.nv-noData').data([noData]);
12951
12952
noDataText.enter().append('text')
12953
.attr('class', 'nvd3 nv-noData')
12954
.attr('dy', '-.7em')
12955
.style('text-anchor', 'middle');
12956
12957
noDataText
12958
.attr('x', margin.left + availableWidth / 2)
12959
.attr('y', margin.top + availableHeight / 2)
12960
.text(function(d) { return d });
12961
12962
return chart;
12963
} else {
12964
container.selectAll('.nv-noData').remove();
12965
}
12966
12967
var currentValue = sparkline.y()(data[data.length-1], data.length-1);
12968
12969
//------------------------------------------------------------
12970
12971
12972
12973
//------------------------------------------------------------
12974
// Setup Scales
12975
12976
x = sparkline.xScale();
12977
y = sparkline.yScale();
12978
12979
//------------------------------------------------------------
12980
12981
12982
//------------------------------------------------------------
12983
// Setup containers and skeleton of chart
12984
12985
var wrap = container.selectAll('g.nv-wrap.nv-sparklineplus').data([data]);
12986
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-sparklineplus');
12987
var gEnter = wrapEnter.append('g');
12988
var g = wrap.select('g');
12989
12990
gEnter.append('g').attr('class', 'nv-sparklineWrap');
12991
gEnter.append('g').attr('class', 'nv-valueWrap');
12992
gEnter.append('g').attr('class', 'nv-hoverArea');
12993
12994
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
12995
12996
//------------------------------------------------------------
12997
12998
12999
//------------------------------------------------------------
13000
// Main Chart Component(s)
13001
13002
var sparklineWrap = g.select('.nv-sparklineWrap');
13003
13004
sparkline
13005
.width(availableWidth)
13006
.height(availableHeight);
13007
13008
sparklineWrap
13009
.call(sparkline);
13010
13011
//------------------------------------------------------------
13012
13013
13014
var valueWrap = g.select('.nv-valueWrap');
13015
13016
var value = valueWrap.selectAll('.nv-currentValue')
13017
.data([currentValue]);
13018
13019
value.enter().append('text').attr('class', 'nv-currentValue')
13020
.attr('dx', rightAlignValue ? -8 : 8)
13021
.attr('dy', '.9em')
13022
.style('text-anchor', rightAlignValue ? 'end' : 'start');
13023
13024
value
13025
.attr('x', availableWidth + (rightAlignValue ? margin.right : 0))
13026
.attr('y', alignValue ? function(d) { return y(d) } : 0)
13027
.style('fill', sparkline.color()(data[data.length-1], data.length-1))
13028
.text(yTickFormat(currentValue));
13029
13030
13031
13032
gEnter.select('.nv-hoverArea').append('rect')
13033
.on('mousemove', sparklineHover)
13034
.on('click', function() { paused = !paused })
13035
.on('mouseout', function() { index = []; updateValueLine(); });
13036
//.on('mouseout', function() { index = null; updateValueLine(); });
13037
13038
g.select('.nv-hoverArea rect')
13039
.attr('transform', function(d) { return 'translate(' + -margin.left + ',' + -margin.top + ')' })
13040
.attr('width', availableWidth + margin.left + margin.right)
13041
.attr('height', availableHeight + margin.top);
13042
13043
13044
13045
function updateValueLine() { //index is currently global (within the chart), may or may not keep it that way
13046
if (paused) return;
13047
13048
var hoverValue = g.selectAll('.nv-hoverValue').data(index)
13049
13050
var hoverEnter = hoverValue.enter()
13051
.append('g').attr('class', 'nv-hoverValue')
13052
.style('stroke-opacity', 0)
13053
.style('fill-opacity', 0);
13054
13055
hoverValue.exit()
13056
.transition().duration(250)
13057
.style('stroke-opacity', 0)
13058
.style('fill-opacity', 0)
13059
.remove();
13060
13061
hoverValue
13062
.attr('transform', function(d) { return 'translate(' + x(sparkline.x()(data[d],d)) + ',0)' })
13063
.transition().duration(250)
13064
.style('stroke-opacity', 1)
13065
.style('fill-opacity', 1);
13066
13067
if (!index.length) return;
13068
13069
hoverEnter.append('line')
13070
.attr('x1', 0)
13071
.attr('y1', -margin.top)
13072
.attr('x2', 0)
13073
.attr('y2', availableHeight);
13074
13075
13076
hoverEnter.append('text').attr('class', 'nv-xValue')
13077
.attr('x', -6)
13078
.attr('y', -margin.top)
13079
.attr('text-anchor', 'end')
13080
.attr('dy', '.9em')
13081
13082
13083
g.select('.nv-hoverValue .nv-xValue')
13084
.text(xTickFormat(sparkline.x()(data[index[0]], index[0])));
13085
13086
hoverEnter.append('text').attr('class', 'nv-yValue')
13087
.attr('x', 6)
13088
.attr('y', -margin.top)
13089
.attr('text-anchor', 'start')
13090
.attr('dy', '.9em')
13091
13092
g.select('.nv-hoverValue .nv-yValue')
13093
.text(yTickFormat(sparkline.y()(data[index[0]], index[0])));
13094
13095
}
13096
13097
13098
function sparklineHover() {
13099
if (paused) return;
13100
13101
var pos = d3.mouse(this)[0] - margin.left;
13102
13103
function getClosestIndex(data, x) {
13104
var distance = Math.abs(sparkline.x()(data[0], 0) - x);
13105
var closestIndex = 0;
13106
for (var i = 0; i < data.length; i++){
13107
if (Math.abs(sparkline.x()(data[i], i) - x) < distance) {
13108
distance = Math.abs(sparkline.x()(data[i], i) - x);
13109
closestIndex = i;
13110
}
13111
}
13112
return closestIndex;
13113
}
13114
13115
index = [getClosestIndex(data, Math.round(x.invert(pos)))];
13116
13117
updateValueLine();
13118
}
13119
13120
});
13121
13122
return chart;
13123
}
13124
13125
13126
//============================================================
13127
// Expose Public Variables
13128
//------------------------------------------------------------
13129
13130
// expose chart's sub-components
13131
chart.sparkline = sparkline;
13132
13133
d3.rebind(chart, sparkline, 'x', 'y', 'xScale', 'yScale', 'color');
13134
13135
chart.options = nv.utils.optionsFunc.bind(chart);
13136
13137
chart.margin = function(_) {
13138
if (!arguments.length) return margin;
13139
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13140
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13141
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13142
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13143
return chart;
13144
};
13145
13146
chart.width = function(_) {
13147
if (!arguments.length) return width;
13148
width = _;
13149
return chart;
13150
};
13151
13152
chart.height = function(_) {
13153
if (!arguments.length) return height;
13154
height = _;
13155
return chart;
13156
};
13157
13158
chart.xTickFormat = function(_) {
13159
if (!arguments.length) return xTickFormat;
13160
xTickFormat = _;
13161
return chart;
13162
};
13163
13164
chart.yTickFormat = function(_) {
13165
if (!arguments.length) return yTickFormat;
13166
yTickFormat = _;
13167
return chart;
13168
};
13169
13170
chart.showValue = function(_) {
13171
if (!arguments.length) return showValue;
13172
showValue = _;
13173
return chart;
13174
};
13175
13176
chart.alignValue = function(_) {
13177
if (!arguments.length) return alignValue;
13178
alignValue = _;
13179
return chart;
13180
};
13181
13182
chart.rightAlignValue = function(_) {
13183
if (!arguments.length) return rightAlignValue;
13184
rightAlignValue = _;
13185
return chart;
13186
};
13187
13188
chart.noData = function(_) {
13189
if (!arguments.length) return noData;
13190
noData = _;
13191
return chart;
13192
};
13193
13194
//============================================================
13195
13196
13197
return chart;
13198
}
13199
13200
nv.models.stackedArea = function() {
13201
"use strict";
13202
//============================================================
13203
// Public Variables with Default Settings
13204
//------------------------------------------------------------
13205
13206
var margin = {top: 0, right: 0, bottom: 0, left: 0}
13207
, width = 960
13208
, height = 500
13209
, color = nv.utils.defaultColor() // a function that computes the color
13210
, id = Math.floor(Math.random() * 100000) //Create semi-unique ID incase user doesn't selet one
13211
, getX = function(d) { return d.x } // accessor to get the x value from a data point
13212
, getY = function(d) { return d.y } // accessor to get the y value from a data point
13213
, style = 'stack'
13214
, offset = 'zero'
13215
, order = 'default'
13216
, interpolate = 'linear' // controls the line interpolation
13217
, clipEdge = false // if true, masks lines within x and y scale
13218
, x //can be accessed via chart.xScale()
13219
, y //can be accessed via chart.yScale()
13220
, scatter = nv.models.scatter()
13221
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'areaClick', 'areaMouseover', 'areaMouseout')
13222
;
13223
13224
scatter
13225
.size(2.2) // default size
13226
.sizeDomain([2.2,2.2]) // all the same size by default
13227
;
13228
13229
/************************************
13230
* offset:
13231
* 'wiggle' (stream)
13232
* 'zero' (stacked)
13233
* 'expand' (normalize to 100%)
13234
* 'silhouette' (simple centered)
13235
*
13236
* order:
13237
* 'inside-out' (stream)
13238
* 'default' (input order)
13239
************************************/
13240
13241
//============================================================
13242
13243
13244
function chart(selection) {
13245
selection.each(function(data) {
13246
var availableWidth = width - margin.left - margin.right,
13247
availableHeight = height - margin.top - margin.bottom,
13248
container = d3.select(this);
13249
13250
//------------------------------------------------------------
13251
// Setup Scales
13252
13253
x = scatter.xScale();
13254
y = scatter.yScale();
13255
13256
//------------------------------------------------------------
13257
13258
13259
// Injecting point index into each point because d3.layout.stack().out does not give index
13260
data = data.map(function(aseries, i) {
13261
aseries.seriesIndex = i;
13262
aseries.values = aseries.values.map(function(d, j) {
13263
d.index = j;
13264
d.seriesIndex = i;
13265
return d;
13266
})
13267
return aseries;
13268
});
13269
13270
var dataFiltered = data.filter(function(series) {
13271
return !series.disabled;
13272
});
13273
13274
data = d3.layout.stack()
13275
.order(order)
13276
.offset(offset)
13277
.values(function(d) { return d.values }) //TODO: make values customizeable in EVERY model in this fashion
13278
.x(getX)
13279
.y(getY)
13280
.out(function(d, y0, y) {
13281
var yHeight = (getY(d) === 0) ? 0 : y;
13282
d.display = {
13283
y: yHeight,
13284
y0: y0
13285
};
13286
})
13287
(dataFiltered);
13288
13289
13290
//------------------------------------------------------------
13291
// Setup containers and skeleton of chart
13292
13293
var wrap = container.selectAll('g.nv-wrap.nv-stackedarea').data([data]);
13294
var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedarea');
13295
var defsEnter = wrapEnter.append('defs');
13296
var gEnter = wrapEnter.append('g');
13297
var g = wrap.select('g');
13298
13299
gEnter.append('g').attr('class', 'nv-areaWrap');
13300
gEnter.append('g').attr('class', 'nv-scatterWrap');
13301
13302
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13303
13304
//------------------------------------------------------------
13305
13306
13307
scatter
13308
.width(availableWidth)
13309
.height(availableHeight)
13310
.x(getX)
13311
.y(function(d) { return d.display.y + d.display.y0 })
13312
.forceY([0])
13313
.color(data.map(function(d,i) {
13314
return d.color || color(d, d.seriesIndex);
13315
}));
13316
13317
13318
var scatterWrap = g.select('.nv-scatterWrap')
13319
.datum(data);
13320
13321
scatterWrap.call(scatter);
13322
13323
defsEnter.append('clipPath')
13324
.attr('id', 'nv-edge-clip-' + id)
13325
.append('rect');
13326
13327
wrap.select('#nv-edge-clip-' + id + ' rect')
13328
.attr('width', availableWidth)
13329
.attr('height', availableHeight);
13330
13331
g .attr('clip-path', clipEdge ? 'url(#nv-edge-clip-' + id + ')' : '');
13332
13333
var area = d3.svg.area()
13334
.x(function(d,i) { return x(getX(d,i)) })
13335
.y0(function(d) {
13336
return y(d.display.y0)
13337
})
13338
.y1(function(d) {
13339
return y(d.display.y + d.display.y0)
13340
})
13341
.interpolate(interpolate);
13342
13343
var zeroArea = d3.svg.area()
13344
.x(function(d,i) { return x(getX(d,i)) })
13345
.y0(function(d) { return y(d.display.y0) })
13346
.y1(function(d) { return y(d.display.y0) });
13347
13348
13349
var path = g.select('.nv-areaWrap').selectAll('path.nv-area')
13350
.data(function(d) { return d });
13351
13352
path.enter().append('path').attr('class', function(d,i) { return 'nv-area nv-area-' + i })
13353
.attr('d', function(d,i){
13354
return zeroArea(d.values, d.seriesIndex);
13355
})
13356
.on('mouseover', function(d,i) {
13357
d3.select(this).classed('hover', true);
13358
dispatch.areaMouseover({
13359
point: d,
13360
series: d.key,
13361
pos: [d3.event.pageX, d3.event.pageY],
13362
seriesIndex: i
13363
});
13364
})
13365
.on('mouseout', function(d,i) {
13366
d3.select(this).classed('hover', false);
13367
dispatch.areaMouseout({
13368
point: d,
13369
series: d.key,
13370
pos: [d3.event.pageX, d3.event.pageY],
13371
seriesIndex: i
13372
});
13373
})
13374
.on('click', function(d,i) {
13375
d3.select(this).classed('hover', false);
13376
dispatch.areaClick({
13377
point: d,
13378
series: d.key,
13379
pos: [d3.event.pageX, d3.event.pageY],
13380
seriesIndex: i
13381
});
13382
})
13383
path.exit().transition()
13384
.attr('d', function(d,i) { return zeroArea(d.values,i) })
13385
.remove();
13386
path
13387
.style('fill', function(d,i){
13388
return d.color || color(d, d.seriesIndex)
13389
})
13390
.style('stroke', function(d,i){ return d.color || color(d, d.seriesIndex) });
13391
path.transition()
13392
.attr('d', function(d,i) {
13393
return area(d.values,i)
13394
});
13395
13396
13397
13398
//============================================================
13399
// Event Handling/Dispatching (in chart's scope)
13400
//------------------------------------------------------------
13401
13402
scatter.dispatch.on('elementMouseover.area', function(e) {
13403
g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', true);
13404
});
13405
scatter.dispatch.on('elementMouseout.area', function(e) {
13406
g.select('.nv-chart-' + id + ' .nv-area-' + e.seriesIndex).classed('hover', false);
13407
});
13408
13409
//============================================================
13410
13411
});
13412
13413
13414
return chart;
13415
}
13416
13417
13418
//============================================================
13419
// Event Handling/Dispatching (out of chart's scope)
13420
//------------------------------------------------------------
13421
13422
scatter.dispatch.on('elementClick.area', function(e) {
13423
dispatch.areaClick(e);
13424
})
13425
scatter.dispatch.on('elementMouseover.tooltip', function(e) {
13426
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13427
dispatch.tooltipShow(e);
13428
});
13429
scatter.dispatch.on('elementMouseout.tooltip', function(e) {
13430
dispatch.tooltipHide(e);
13431
});
13432
13433
//============================================================
13434
13435
13436
//============================================================
13437
// Global getters and setters
13438
//------------------------------------------------------------
13439
13440
chart.dispatch = dispatch;
13441
chart.scatter = scatter;
13442
13443
d3.rebind(chart, scatter, 'interactive', 'size', 'xScale', 'yScale', 'zScale', 'xDomain', 'yDomain', 'xRange', 'yRange',
13444
'sizeDomain', 'forceX', 'forceY', 'forceSize', 'clipVoronoi', 'useVoronoi','clipRadius','highlightPoint','clearHighlights');
13445
13446
chart.options = nv.utils.optionsFunc.bind(chart);
13447
13448
chart.x = function(_) {
13449
if (!arguments.length) return getX;
13450
getX = d3.functor(_);
13451
return chart;
13452
};
13453
13454
chart.y = function(_) {
13455
if (!arguments.length) return getY;
13456
getY = d3.functor(_);
13457
return chart;
13458
}
13459
13460
chart.margin = function(_) {
13461
if (!arguments.length) return margin;
13462
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
13463
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
13464
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
13465
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
13466
return chart;
13467
};
13468
13469
chart.width = function(_) {
13470
if (!arguments.length) return width;
13471
width = _;
13472
return chart;
13473
};
13474
13475
chart.height = function(_) {
13476
if (!arguments.length) return height;
13477
height = _;
13478
return chart;
13479
};
13480
13481
chart.clipEdge = function(_) {
13482
if (!arguments.length) return clipEdge;
13483
clipEdge = _;
13484
return chart;
13485
};
13486
13487
chart.color = function(_) {
13488
if (!arguments.length) return color;
13489
color = nv.utils.getColor(_);
13490
return chart;
13491
};
13492
13493
chart.offset = function(_) {
13494
if (!arguments.length) return offset;
13495
offset = _;
13496
return chart;
13497
};
13498
13499
chart.order = function(_) {
13500
if (!arguments.length) return order;
13501
order = _;
13502
return chart;
13503
};
13504
13505
//shortcut for offset + order
13506
chart.style = function(_) {
13507
if (!arguments.length) return style;
13508
style = _;
13509
13510
switch (style) {
13511
case 'stack':
13512
chart.offset('zero');
13513
chart.order('default');
13514
break;
13515
case 'stream':
13516
chart.offset('wiggle');
13517
chart.order('inside-out');
13518
break;
13519
case 'stream-center':
13520
chart.offset('silhouette');
13521
chart.order('inside-out');
13522
break;
13523
case 'expand':
13524
chart.offset('expand');
13525
chart.order('default');
13526
break;
13527
}
13528
13529
return chart;
13530
};
13531
13532
chart.interpolate = function(_) {
13533
if (!arguments.length) return interpolate;
13534
interpolate = _;
13535
return chart;
13536
};
13537
//============================================================
13538
13539
13540
return chart;
13541
}
13542
13543
nv.models.stackedAreaChart = function() {
13544
"use strict";
13545
//============================================================
13546
// Public Variables with Default Settings
13547
//------------------------------------------------------------
13548
13549
var stacked = nv.models.stackedArea()
13550
, xAxis = nv.models.axis()
13551
, yAxis = nv.models.axis()
13552
, legend = nv.models.legend()
13553
, controls = nv.models.legend()
13554
, interactiveLayer = nv.interactiveGuideline()
13555
;
13556
13557
var margin = {top: 30, right: 25, bottom: 50, left: 60}
13558
, width = null
13559
, height = null
13560
, color = nv.utils.defaultColor() // a function that takes in d, i and returns color
13561
, showControls = true
13562
, showLegend = true
13563
, showXAxis = true
13564
, showYAxis = true
13565
, rightAlignYAxis = false
13566
, useInteractiveGuideline = false
13567
, tooltips = true
13568
, tooltip = function(key, x, y, e, graph) {
13569
return '<h3>' + key + '</h3>' +
13570
'<p>' + y + ' on ' + x + '</p>'
13571
}
13572
, x //can be accessed via chart.xScale()
13573
, y //can be accessed via chart.yScale()
13574
, yAxisTickFormat = d3.format(',.2f')
13575
, state = { style: stacked.style() }
13576
, defaultState = null
13577
, noData = 'No Data Available.'
13578
, dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
13579
, controlWidth = 250
13580
, cData = ['Stacked','Stream','Expanded']
13581
, transitionDuration = 250
13582
;
13583
13584
xAxis
13585
.orient('bottom')
13586
.tickPadding(7)
13587
;
13588
yAxis
13589
.orient((rightAlignYAxis) ? 'right' : 'left')
13590
;
13591
13592
controls.updateState(false);
13593
//============================================================
13594
13595
13596
//============================================================
13597
// Private Variables
13598
//------------------------------------------------------------
13599
13600
var showTooltip = function(e, offsetElement) {
13601
var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
13602
top = e.pos[1] + ( offsetElement.offsetTop || 0),
13603
x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
13604
y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
13605
content = tooltip(e.series.key, x, y, e, chart);
13606
13607
nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
13608
};
13609
13610
//============================================================
13611
13612
13613
function chart(selection) {
13614
selection.each(function(data) {
13615
var container = d3.select(this),
13616
that = this;
13617
13618
var availableWidth = (width || parseInt(container.style('width')) || 960)
13619
- margin.left - margin.right,
13620
availableHeight = (height || parseInt(container.style('height')) || 400)
13621
- margin.top - margin.bottom;
13622
13623
chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
13624
chart.container = this;
13625
13626
//set state.disabled
13627
state.disabled = data.map(function(d) { return !!d.disabled });
13628
13629
if (!defaultState) {
13630
var key;
13631
defaultState = {};
13632
for (key in state) {
13633
if (state[key] instanceof Array)
13634
defaultState[key] = state[key].slice(0);
13635
else
13636
defaultState[key] = state[key];
13637
}
13638
}
13639
13640
//------------------------------------------------------------
13641
// Display No Data message if there's nothing to show.
13642
13643
if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
13644
var noDataText = container.selectAll('.nv-noData').data([noData]);
13645
13646
noDataText.enter().append('text')
13647
.attr('class', 'nvd3 nv-noData')
13648
.attr('dy', '-.7em')
13649
.style('text-anchor', 'middle');
13650
13651
noDataText
13652
.attr('x', margin.left + availableWidth / 2)
13653
.attr('y', margin.top + availableHeight / 2)
13654
.text(function(d) { return d });
13655
13656
return chart;
13657
} else {
13658
container.selectAll('.nv-noData').remove();
13659
}
13660
13661
//------------------------------------------------------------
13662
13663
13664
//------------------------------------------------------------
13665
// Setup Scales
13666
13667
x = stacked.xScale();
13668
y = stacked.yScale();
13669
13670
//------------------------------------------------------------
13671
13672
13673
//------------------------------------------------------------
13674
// Setup containers and skeleton of chart
13675
13676
var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
13677
var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
13678
var g = wrap.select('g');
13679
13680
gEnter.append("rect").style("opacity",0);
13681
gEnter.append('g').attr('class', 'nv-x nv-axis');
13682
gEnter.append('g').attr('class', 'nv-y nv-axis');
13683
gEnter.append('g').attr('class', 'nv-stackedWrap');
13684
gEnter.append('g').attr('class', 'nv-legendWrap');
13685
gEnter.append('g').attr('class', 'nv-controlsWrap');
13686
gEnter.append('g').attr('class', 'nv-interactive');
13687
13688
g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
13689
//------------------------------------------------------------
13690
// Legend
13691
13692
if (showLegend) {
13693
var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
13694
legend
13695
.width(legendWidth);
13696
13697
g.select('.nv-legendWrap')
13698
.datum(data)
13699
.call(legend);
13700
13701
if ( margin.top != legend.height()) {
13702
margin.top = legend.height();
13703
availableHeight = (height || parseInt(container.style('height')) || 400)
13704
- margin.top - margin.bottom;
13705
}
13706
13707
g.select('.nv-legendWrap')
13708
.attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
13709
}
13710
13711
//------------------------------------------------------------
13712
13713
13714
//------------------------------------------------------------
13715
// Controls
13716
13717
if (showControls) {
13718
var controlsData = [
13719
{ key: 'Stacked', disabled: stacked.offset() != 'zero' },
13720
{ key: 'Stream', disabled: stacked.offset() != 'wiggle' },
13721
{ key: 'Expanded', disabled: stacked.offset() != 'expand' }
13722
];
13723
13724
controlWidth = (cData.length/3) * 260;
13725
13726
controlsData = controlsData.filter(function(d) {
13727
return cData.indexOf(d.key) > -1;
13728
})
13729
13730
controls
13731
.width( controlWidth )
13732
.color(['#444', '#444', '#444']);
13733
13734
g.select('.nv-controlsWrap')
13735
.datum(controlsData)
13736
.call(controls);
13737
13738
13739
if ( margin.top != Math.max(controls.height(), legend.height()) ) {
13740
margin.top = Math.max(controls.height(), legend.height());
13741
availableHeight = (height || parseInt(container.style('height')) || 400)
13742
- margin.top - margin.bottom;
13743
}
13744
13745
13746
g.select('.nv-controlsWrap')
13747
.attr('transform', 'translate(0,' + (-margin.top) +')');
13748
}
13749
13750
//------------------------------------------------------------
13751
13752
13753
wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
13754
13755
if (rightAlignYAxis) {
13756
g.select(".nv-y.nv-axis")
13757
.attr("transform", "translate(" + availableWidth + ",0)");
13758
}
13759
13760
//------------------------------------------------------------
13761
// Main Chart Component(s)
13762
13763
//------------------------------------------------------------
13764
//Set up interactive layer
13765
if (useInteractiveGuideline) {
13766
interactiveLayer
13767
.width(availableWidth)
13768
.height(availableHeight)
13769
.margin({left: margin.left, top: margin.top})
13770
.svgContainer(container)
13771
.xScale(x);
13772
wrap.select(".nv-interactive").call(interactiveLayer);
13773
}
13774
13775
stacked
13776
.width(availableWidth)
13777
.height(availableHeight)
13778
13779
var stackedWrap = g.select('.nv-stackedWrap')
13780
.datum(data);
13781
13782
stackedWrap.transition().call(stacked);
13783
13784
//------------------------------------------------------------
13785
13786
13787
//------------------------------------------------------------
13788
// Setup Axes
13789
13790
if (showXAxis) {
13791
xAxis
13792
.scale(x)
13793
.ticks( availableWidth / 100 )
13794
.tickSize( -availableHeight, 0);
13795
13796
g.select('.nv-x.nv-axis')
13797
.attr('transform', 'translate(0,' + availableHeight + ')');
13798
13799
g.select('.nv-x.nv-axis')
13800
.transition().duration(0)
13801
.call(xAxis);
13802
}
13803
13804
if (showYAxis) {
13805
yAxis
13806
.scale(y)
13807
.ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
13808
.tickSize(-availableWidth, 0)
13809
.setTickFormat(stacked.offset() == 'expand' ? d3.format('%') : yAxisTickFormat);
13810
13811
g.select('.nv-y.nv-axis')
13812
.transition().duration(0)
13813
.call(yAxis);
13814
}
13815
13816
//------------------------------------------------------------
13817
13818
13819
//============================================================
13820
// Event Handling/Dispatching (in chart's scope)
13821
//------------------------------------------------------------
13822
13823
stacked.dispatch.on('areaClick.toggle', function(e) {
13824
if (data.filter(function(d) { return !d.disabled }).length === 1)
13825
data = data.map(function(d) {
13826
d.disabled = false;
13827
return d
13828
});
13829
else
13830
data = data.map(function(d,i) {
13831
d.disabled = (i != e.seriesIndex);
13832
return d
13833
});
13834
13835
state.disabled = data.map(function(d) { return !!d.disabled });
13836
dispatch.stateChange(state);
13837
13838
chart.update();
13839
});
13840
13841
legend.dispatch.on('stateChange', function(newState) {
13842
state.disabled = newState.disabled;
13843
dispatch.stateChange(state);
13844
chart.update();
13845
});
13846
13847
controls.dispatch.on('legendClick', function(d,i) {
13848
if (!d.disabled) return;
13849
13850
controlsData = controlsData.map(function(s) {
13851
s.disabled = true;
13852
return s;
13853
});
13854
d.disabled = false;
13855
13856
switch (d.key) {
13857
case 'Stacked':
13858
stacked.style('stack');
13859
break;
13860
case 'Stream':
13861
stacked.style('stream');
13862
break;
13863
case 'Expanded':
13864
stacked.style('expand');
13865
break;
13866
}
13867
13868
state.style = stacked.style();
13869
dispatch.stateChange(state);
13870
13871
chart.update();
13872
});
13873
13874
13875
interactiveLayer.dispatch.on('elementMousemove', function(e) {
13876
stacked.clearHighlights();
13877
var singlePoint, pointIndex, pointXLocation, allData = [];
13878
data
13879
.filter(function(series, i) {
13880
series.seriesIndex = i;
13881
return !series.disabled;
13882
})
13883
.forEach(function(series,i) {
13884
pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
13885
stacked.highlightPoint(i, pointIndex, true);
13886
var point = series.values[pointIndex];
13887
if (typeof point === 'undefined') return;
13888
if (typeof singlePoint === 'undefined') singlePoint = point;
13889
if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
13890
allData.push({
13891
key: series.key,
13892
value: chart.y()(point, pointIndex),
13893
color: color(series,series.seriesIndex)
13894
});
13895
});
13896
13897
var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
13898
interactiveLayer.tooltip
13899
.position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
13900
.chartContainer(that.parentNode)
13901
.enabled(tooltips)
13902
.valueFormatter(function(d,i) {
13903
return yAxis.tickFormat()(d);
13904
})
13905
.data(
13906
{
13907
value: xValue,
13908
series: allData
13909
}
13910
)();
13911
13912
interactiveLayer.renderGuideLine(pointXLocation);
13913
13914
});
13915
13916
interactiveLayer.dispatch.on("elementMouseout",function(e) {
13917
dispatch.tooltipHide();
13918
stacked.clearHighlights();
13919
});
13920
13921
13922
dispatch.on('tooltipShow', function(e) {
13923
if (tooltips) showTooltip(e, that.parentNode);
13924
});
13925
13926
// Update chart from a state object passed to event handler
13927
dispatch.on('changeState', function(e) {
13928
13929
if (typeof e.disabled !== 'undefined') {
13930
data.forEach(function(series,i) {
13931
series.disabled = e.disabled[i];
13932
});
13933
13934
state.disabled = e.disabled;
13935
}
13936
13937
if (typeof e.style !== 'undefined') {
13938
stacked.style(e.style);
13939
}
13940
13941
chart.update();
13942
});
13943
13944
});
13945
13946
13947
return chart;
13948
}
13949
13950
13951
//============================================================
13952
// Event Handling/Dispatching (out of chart's scope)
13953
//------------------------------------------------------------
13954
13955
stacked.dispatch.on('tooltipShow', function(e) {
13956
//disable tooltips when value ~= 0
13957
//// TODO: consider removing points from voronoi that have 0 value instead of this hack
13958
/*
13959
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
13960
setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
13961
return false;
13962
}
13963
*/
13964
13965
e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
13966
dispatch.tooltipShow(e);
13967
});
13968
13969
stacked.dispatch.on('tooltipHide', function(e) {
13970
dispatch.tooltipHide(e);
13971
});
13972
13973
dispatch.on('tooltipHide', function() {
13974
if (tooltips) nv.tooltip.cleanup();
13975
});
13976
13977
//============================================================
13978
13979
13980
//============================================================
13981
// Expose Public Variables
13982
//------------------------------------------------------------
13983
13984
// expose chart's sub-components
13985
chart.dispatch = dispatch;
13986
chart.stacked = stacked;
13987
chart.legend = legend;
13988
chart.controls = controls;
13989
chart.xAxis = xAxis;
13990
chart.yAxis = yAxis;
13991
chart.interactiveLayer = interactiveLayer;
13992
13993
d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
13994
13995
chart.options = nv.utils.optionsFunc.bind(chart);
13996
13997
chart.margin = function(_) {
13998
if (!arguments.length) return margin;
13999
margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
14000
margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
14001
margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
14002
margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
14003
return chart;
14004
};
14005
14006
chart.width = function(_) {
14007
if (!arguments.length) return width;
14008
width = _;
14009
return chart;
14010
};
14011
14012
chart.height = function(_) {
14013
if (!arguments.length) return height;
14014
height = _;
14015
return chart;
14016
};
14017
14018
chart.color = function(_) {
14019
if (!arguments.length) return color;
14020
color = nv.utils.getColor(_);
14021
legend.color(color);
14022
stacked.color(color);
14023
return chart;
14024
};
14025
14026
chart.showControls = function(_) {
14027
if (!arguments.length) return showControls;
14028
showControls = _;
14029
return chart;
14030
};
14031
14032
chart.showLegend = function(_) {
14033
if (!arguments.length) return showLegend;
14034
showLegend = _;
14035
return chart;
14036
};
14037
14038
chart.showXAxis = function(_) {
14039
if (!arguments.length) return showXAxis;
14040
showXAxis = _;
14041
return chart;
14042
};
14043
14044
chart.showYAxis = function(_) {
14045
if (!arguments.length) return showYAxis;
14046
showYAxis = _;
14047
return chart;
14048
};
14049
14050
chart.rightAlignYAxis = function(_) {
14051
if(!arguments.length) return rightAlignYAxis;
14052
rightAlignYAxis = _;
14053
yAxis.orient( (_) ? 'right' : 'left');
14054
return chart;
14055
};
14056
14057
chart.useInteractiveGuideline = function(_) {
14058
if(!arguments.length) return useInteractiveGuideline;
14059
useInteractiveGuideline = _;
14060
if (_ === true) {
14061
chart.interactive(false);
14062
chart.useVoronoi(false);
14063
}
14064
return chart;
14065
};
14066
14067
chart.tooltip = function(_) {
14068
if (!arguments.length) return tooltip;
14069
tooltip = _;
14070
return chart;
14071
};
14072
14073
chart.tooltips = function(_) {
14074
if (!arguments.length) return tooltips;
14075
tooltips = _;
14076
return chart;
14077
};
14078
14079
chart.tooltipContent = function(_) {
14080
if (!arguments.length) return tooltip;
14081
tooltip = _;
14082
return chart;
14083
};
14084
14085
chart.state = function(_) {
14086
if (!arguments.length) return state;
14087
state = _;
14088
return chart;
14089
};
14090
14091
chart.defaultState = function(_) {
14092
if (!arguments.length) return defaultState;
14093
defaultState = _;
14094
return chart;
14095
};
14096
14097
chart.noData = function(_) {
14098
if (!arguments.length) return noData;
14099
noData = _;
14100
return chart;
14101
};
14102
14103
chart.transitionDuration = function(_) {
14104
if (!arguments.length) return transitionDuration;
14105
transitionDuration = _;
14106
return chart;
14107
};
14108
14109
chart.controlsData = function(_) {
14110
if (!arguments.length) return cData;
14111
cData = _;
14112
return chart;
14113
};
14114
14115
yAxis.setTickFormat = yAxis.tickFormat;
14116
14117
yAxis.tickFormat = function(_) {
14118
if (!arguments.length) return yAxisTickFormat;
14119
yAxisTickFormat = _;
14120
return yAxis;
14121
};
14122
14123
14124
//============================================================
14125
14126
return chart;
14127
}
14128
})();
14129