2 nv.models.stackedAreaChart = function() {
4 //============================================================
5 // Public Variables with Default Settings
6 //------------------------------------------------------------
8 var stacked = nv.models.stackedArea()
9 , xAxis = nv.models.axis()
10 , yAxis = nv.models.axis()
11 , legend = nv.models.legend()
12 , controls = nv.models.legend()
13 , interactiveLayer = nv.interactiveGuideline()
16 var margin = {top: 30, right: 25, bottom: 50, left: 60}
19 , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
24 , rightAlignYAxis = false
25 , useInteractiveGuideline = false
27 , tooltip = function(key, x, y, e, graph) {
28 return '<h3>' + key + '</h3>' +
29 '<p>' + y + ' on ' + x + '</p>'
31 , x //can be accessed via chart.xScale()
32 , y //can be accessed via chart.yScale()
33 , yAxisTickFormat = d3.format(',.2f')
34 , state = { style: stacked.style() }
36 , noData = 'No Data Available.'
37 , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
39 , cData = ['Stacked','Stream','Expanded']
41 , transitionDuration = 250
49 .orient((rightAlignYAxis) ? 'right' : 'left')
52 controls.updateState(false);
53 //============================================================
56 //============================================================
58 //------------------------------------------------------------
60 var showTooltip = function(e, offsetElement) {
61 var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
62 top = e.pos[1] + ( offsetElement.offsetTop || 0),
63 x = xAxis.tickFormat()(stacked.x()(e.point, e.pointIndex)),
64 y = yAxis.tickFormat()(stacked.y()(e.point, e.pointIndex)),
65 content = tooltip(e.series.key, x, y, e, chart);
67 nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
70 //============================================================
73 function chart(selection) {
74 selection.each(function(data) {
75 var container = d3.select(this),
78 var availableWidth = (width || parseInt(container.style('width')) || 960)
79 - margin.left - margin.right,
80 availableHeight = (height || parseInt(container.style('height')) || 400)
81 - margin.top - margin.bottom;
83 chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
84 chart.container = this;
87 state.disabled = data.map(function(d) { return !!d.disabled });
93 if (state[key] instanceof Array)
94 defaultState[key] = state[key].slice(0);
96 defaultState[key] = state[key];
100 //------------------------------------------------------------
101 // Display No Data message if there's nothing to show.
103 if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
104 var noDataText = container.selectAll('.nv-noData').data([noData]);
106 noDataText.enter().append('text')
107 .attr('class', 'nvd3 nv-noData')
109 .style('text-anchor', 'middle');
112 .attr('x', margin.left + availableWidth / 2)
113 .attr('y', margin.top + availableHeight / 2)
114 .text(function(d) { return d });
118 container.selectAll('.nv-noData').remove();
121 //------------------------------------------------------------
124 //------------------------------------------------------------
127 x = stacked.xScale();
128 y = stacked.yScale();
130 //------------------------------------------------------------
133 //------------------------------------------------------------
134 // Setup containers and skeleton of chart
136 var wrap = container.selectAll('g.nv-wrap.nv-stackedAreaChart').data([data]);
137 var gEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-stackedAreaChart').append('g');
138 var g = wrap.select('g');
140 gEnter.append("rect").style("opacity",0);
141 gEnter.append('g').attr('class', 'nv-x nv-axis');
142 gEnter.append('g').attr('class', 'nv-y nv-axis');
143 gEnter.append('g').attr('class', 'nv-stackedWrap');
144 gEnter.append('g').attr('class', 'nv-legendWrap');
145 gEnter.append('g').attr('class', 'nv-controlsWrap');
146 gEnter.append('g').attr('class', 'nv-interactive');
148 g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
149 //------------------------------------------------------------
153 var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
157 g.select('.nv-legendWrap')
161 if ( margin.top != legend.height()) {
162 margin.top = legend.height();
163 availableHeight = (height || parseInt(container.style('height')) || 400)
164 - margin.top - margin.bottom;
167 g.select('.nv-legendWrap')
168 .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
171 //------------------------------------------------------------
174 //------------------------------------------------------------
180 key: controlLabels.stacked || 'Stacked',
182 disabled: stacked.style() != 'stack',
186 key: controlLabels.stream || 'Stream',
188 disabled: stacked.style() != 'stream',
192 key: controlLabels.expanded || 'Expanded',
194 disabled: stacked.style() != 'expand',
198 key: controlLabels.stack_percent || 'Stack %',
199 metaKey: 'Stack_Percent',
200 disabled: stacked.style() != 'stack_percent',
201 style: 'stack_percent'
205 controlWidth = (cData.length/3) * 260;
207 controlsData = controlsData.filter(function(d) {
208 return cData.indexOf(d.metaKey) !== -1;
212 .width( controlWidth )
213 .color(['#444', '#444', '#444']);
215 g.select('.nv-controlsWrap')
220 if ( margin.top != Math.max(controls.height(), legend.height()) ) {
221 margin.top = Math.max(controls.height(), legend.height());
222 availableHeight = (height || parseInt(container.style('height')) || 400)
223 - margin.top - margin.bottom;
227 g.select('.nv-controlsWrap')
228 .attr('transform', 'translate(0,' + (-margin.top) +')');
231 //------------------------------------------------------------
234 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
236 if (rightAlignYAxis) {
237 g.select(".nv-y.nv-axis")
238 .attr("transform", "translate(" + availableWidth + ",0)");
241 //------------------------------------------------------------
242 // Main Chart Component(s)
244 //------------------------------------------------------------
245 //Set up interactive layer
246 if (useInteractiveGuideline) {
248 .width(availableWidth)
249 .height(availableHeight)
250 .margin({left: margin.left, top: margin.top})
251 .svgContainer(container)
253 wrap.select(".nv-interactive").call(interactiveLayer);
257 .width(availableWidth)
258 .height(availableHeight)
260 var stackedWrap = g.select('.nv-stackedWrap')
263 stackedWrap.transition().call(stacked);
265 //------------------------------------------------------------
268 //------------------------------------------------------------
274 .ticks( availableWidth / 100 )
275 .tickSize( -availableHeight, 0);
277 g.select('.nv-x.nv-axis')
278 .attr('transform', 'translate(0,' + availableHeight + ')');
280 g.select('.nv-x.nv-axis')
281 .transition().duration(0)
288 .ticks(stacked.offset() == 'wiggle' ? 0 : availableHeight / 36)
289 .tickSize(-availableWidth, 0)
290 .setTickFormat( (stacked.style() == 'expand' || stacked.style() == 'stack_percent')
291 ? d3.format('%') : yAxisTickFormat);
293 g.select('.nv-y.nv-axis')
294 .transition().duration(0)
298 //------------------------------------------------------------
301 //============================================================
302 // Event Handling/Dispatching (in chart's scope)
303 //------------------------------------------------------------
305 stacked.dispatch.on('areaClick.toggle', function(e) {
306 if (data.filter(function(d) { return !d.disabled }).length === 1)
307 data.forEach(function(d) {
311 data.forEach(function(d,i) {
312 d.disabled = (i != e.seriesIndex);
315 state.disabled = data.map(function(d) { return !!d.disabled });
316 dispatch.stateChange(state);
321 legend.dispatch.on('stateChange', function(newState) {
322 state.disabled = newState.disabled;
323 dispatch.stateChange(state);
327 controls.dispatch.on('legendClick', function(d,i) {
328 if (!d.disabled) return;
330 controlsData = controlsData.map(function(s) {
336 stacked.style(d.style);
339 state.style = stacked.style();
340 dispatch.stateChange(state);
346 interactiveLayer.dispatch.on('elementMousemove', function(e) {
347 stacked.clearHighlights();
348 var singlePoint, pointIndex, pointXLocation, allData = [];
350 .filter(function(series, i) {
351 series.seriesIndex = i;
352 return !series.disabled;
354 .forEach(function(series,i) {
355 pointIndex = nv.interactiveBisect(series.values, e.pointXValue, chart.x());
356 stacked.highlightPoint(i, pointIndex, true);
357 var point = series.values[pointIndex];
358 if (typeof point === 'undefined') return;
359 if (typeof singlePoint === 'undefined') singlePoint = point;
360 if (typeof pointXLocation === 'undefined') pointXLocation = chart.xScale()(chart.x()(point,pointIndex));
362 //If we are in 'expand' mode, use the stacked percent value instead of raw value.
363 var tooltipValue = (stacked.style() == 'expand') ? point.display.y : chart.y()(point,pointIndex);
367 color: color(series,series.seriesIndex),
368 stackedValue: point.display
374 //Highlight the tooltip entry based on which stack the mouse is closest to.
375 if (allData.length > 2) {
376 var yValue = chart.yScale().invert(e.mouseY);
377 var yDistMax = Infinity, indexToHighlight = null;
378 allData.forEach(function(series,i) {
379 if ( yValue >= series.stackedValue.y0 && yValue <= (series.stackedValue.y0 + series.stackedValue.y))
381 indexToHighlight = i;
385 if (indexToHighlight != null)
386 allData[indexToHighlight].highlight = true;
389 var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
391 //If we are in 'expand' mode, force the format to be a percentage.
392 var valueFormatter = (stacked.style() == 'expand') ?
393 function(d,i) {return d3.format(".1%")(d);} :
394 function(d,i) {return yAxis.tickFormat()(d); };
395 interactiveLayer.tooltip
396 .position({left: pointXLocation + margin.left, top: e.mouseY + margin.top})
397 .chartContainer(that.parentNode)
399 .valueFormatter(valueFormatter)
407 interactiveLayer.renderGuideLine(pointXLocation);
411 interactiveLayer.dispatch.on("elementMouseout",function(e) {
412 dispatch.tooltipHide();
413 stacked.clearHighlights();
417 dispatch.on('tooltipShow', function(e) {
418 if (tooltips) showTooltip(e, that.parentNode);
421 // Update chart from a state object passed to event handler
422 dispatch.on('changeState', function(e) {
424 if (typeof e.disabled !== 'undefined') {
425 data.forEach(function(series,i) {
426 series.disabled = e.disabled[i];
429 state.disabled = e.disabled;
432 if (typeof e.style !== 'undefined') {
433 stacked.style(e.style);
446 //============================================================
447 // Event Handling/Dispatching (out of chart's scope)
448 //------------------------------------------------------------
450 stacked.dispatch.on('tooltipShow', function(e) {
451 //disable tooltips when value ~= 0
452 //// TODO: consider removing points from voronoi that have 0 value instead of this hack
454 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
455 setTimeout(function() { d3.selectAll('.point.hover').classed('hover', false) }, 0);
460 e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
461 dispatch.tooltipShow(e);
464 stacked.dispatch.on('tooltipHide', function(e) {
465 dispatch.tooltipHide(e);
468 dispatch.on('tooltipHide', function() {
469 if (tooltips) nv.tooltip.cleanup();
472 //============================================================
475 //============================================================
476 // Expose Public Variables
477 //------------------------------------------------------------
479 // expose chart's sub-components
480 chart.dispatch = dispatch;
481 chart.stacked = stacked;
482 chart.legend = legend;
483 chart.controls = controls;
486 chart.interactiveLayer = interactiveLayer;
488 d3.rebind(chart, stacked, 'x', 'y', 'size', 'xScale', 'yScale', 'xDomain', 'yDomain', 'xRange', 'yRange', 'sizeDomain', 'interactive', 'useVoronoi', 'offset', 'order', 'style', 'clipEdge', 'forceX', 'forceY', 'forceSize', 'interpolate');
490 chart.options = nv.utils.optionsFunc.bind(chart);
492 chart.margin = function(_) {
493 if (!arguments.length) return margin;
494 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
495 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
496 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
497 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
501 chart.width = function(_) {
502 if (!arguments.length) return width;
507 chart.height = function(_) {
508 if (!arguments.length) return height;
513 chart.color = function(_) {
514 if (!arguments.length) return color;
515 color = nv.utils.getColor(_);
517 stacked.color(color);
521 chart.showControls = function(_) {
522 if (!arguments.length) return showControls;
527 chart.showLegend = function(_) {
528 if (!arguments.length) return showLegend;
533 chart.showXAxis = function(_) {
534 if (!arguments.length) return showXAxis;
539 chart.showYAxis = function(_) {
540 if (!arguments.length) return showYAxis;
545 chart.rightAlignYAxis = function(_) {
546 if(!arguments.length) return rightAlignYAxis;
548 yAxis.orient( (_) ? 'right' : 'left');
552 chart.useInteractiveGuideline = function(_) {
553 if(!arguments.length) return useInteractiveGuideline;
554 useInteractiveGuideline = _;
556 chart.interactive(false);
557 chart.useVoronoi(false);
562 chart.tooltip = function(_) {
563 if (!arguments.length) return tooltip;
568 chart.tooltips = function(_) {
569 if (!arguments.length) return tooltips;
574 chart.tooltipContent = function(_) {
575 if (!arguments.length) return tooltip;
580 chart.state = function(_) {
581 if (!arguments.length) return state;
586 chart.defaultState = function(_) {
587 if (!arguments.length) return defaultState;
592 chart.noData = function(_) {
593 if (!arguments.length) return noData;
598 chart.transitionDuration = function(_) {
599 if (!arguments.length) return transitionDuration;
600 transitionDuration = _;
604 chart.controlsData = function(_) {
605 if (!arguments.length) return cData;
610 chart.controlLabels = function(_) {
611 if (!arguments.length) return controlLabels;
612 if (typeof _ !== 'object') return controlLabels;
617 yAxis.setTickFormat = yAxis.tickFormat;
619 yAxis.tickFormat = function(_) {
620 if (!arguments.length) return yAxisTickFormat;
626 //============================================================