Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / static / fusion / raptor / d3 / js / models / stackedAreaChart.js
1
2 nv.models.stackedAreaChart = function() {
3   "use strict";
4   //============================================================
5   // Public Variables with Default Settings
6   //------------------------------------------------------------
7
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()
14     ;
15
16   var margin = {top: 30, right: 25, bottom: 50, left: 60}
17     , width = null
18     , height = null
19     , color = nv.utils.defaultColor() // a function that takes in d, i and returns color
20     , showControls = true
21     , showLegend = true
22     , showXAxis = true
23     , showYAxis = true
24     , rightAlignYAxis = false
25     , useInteractiveGuideline = false
26     , tooltips = true
27     , tooltip = function(key, x, y, e, graph) {
28         return '<h3>' + key + '</h3>' +
29                '<p>' +  y + ' on ' + x + '</p>'
30       }
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() }
35     , defaultState = null
36     , noData = 'No Data Available.'
37     , dispatch = d3.dispatch('tooltipShow', 'tooltipHide', 'stateChange', 'changeState')
38     , controlWidth = 250
39     , cData = ['Stacked','Stream','Expanded']
40     , controlLabels = {}
41     , transitionDuration = 250
42     ;
43
44   xAxis
45     .orient('bottom')
46     .tickPadding(7)
47     ;
48   yAxis
49     .orient((rightAlignYAxis) ? 'right' : 'left')
50     ;
51
52   controls.updateState(false);
53   //============================================================
54
55
56   //============================================================
57   // Private Variables
58   //------------------------------------------------------------
59
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);
66
67     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
68   };
69
70   //============================================================
71
72
73   function chart(selection) {
74     selection.each(function(data) {
75       var container = d3.select(this),
76           that = this;
77
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;
82
83       chart.update = function() { container.transition().duration(transitionDuration).call(chart); };
84       chart.container = this;
85
86       //set state.disabled
87       state.disabled = data.map(function(d) { return !!d.disabled });
88
89       if (!defaultState) {
90         var key;
91         defaultState = {};
92         for (key in state) {
93           if (state[key] instanceof Array)
94             defaultState[key] = state[key].slice(0);
95           else
96             defaultState[key] = state[key];
97         }
98       }
99
100       //------------------------------------------------------------
101       // Display No Data message if there's nothing to show.
102
103       if (!data || !data.length || !data.filter(function(d) { return d.values.length }).length) {
104         var noDataText = container.selectAll('.nv-noData').data([noData]);
105
106         noDataText.enter().append('text')
107           .attr('class', 'nvd3 nv-noData')
108           .attr('dy', '-.7em')
109           .style('text-anchor', 'middle');
110
111         noDataText
112           .attr('x', margin.left + availableWidth / 2)
113           .attr('y', margin.top + availableHeight / 2)
114           .text(function(d) { return d });
115
116         return chart;
117       } else {
118         container.selectAll('.nv-noData').remove();
119       }
120
121       //------------------------------------------------------------
122
123
124       //------------------------------------------------------------
125       // Setup Scales
126
127       x = stacked.xScale();
128       y = stacked.yScale();
129
130       //------------------------------------------------------------
131
132
133       //------------------------------------------------------------
134       // Setup containers and skeleton of chart
135
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');
139
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');
147
148       g.select("rect").attr("width",availableWidth).attr("height",availableHeight);
149       //------------------------------------------------------------
150       // Legend
151
152       if (showLegend) {
153         var legendWidth = (showControls) ? availableWidth - controlWidth : availableWidth;
154         legend
155           .width(legendWidth);
156
157         g.select('.nv-legendWrap')
158             .datum(data)
159             .call(legend);
160
161         if ( margin.top != legend.height()) {
162           margin.top = legend.height();
163           availableHeight = (height || parseInt(container.style('height')) || 400)
164                              - margin.top - margin.bottom;
165         }
166
167         g.select('.nv-legendWrap')
168             .attr('transform', 'translate(' + (availableWidth-legendWidth) + ',' + (-margin.top) +')');
169       }
170
171       //------------------------------------------------------------
172
173
174       //------------------------------------------------------------
175       // Controls
176
177       if (showControls) {
178         var controlsData = [
179           {
180             key: controlLabels.stacked || 'Stacked', 
181             metaKey: 'Stacked', 
182             disabled: stacked.style() != 'stack', 
183             style: 'stack' 
184           },
185           {
186             key: controlLabels.stream || 'Stream', 
187             metaKey: 'Stream', 
188             disabled: stacked.style() != 'stream', 
189             style: 'stream' 
190           },
191           {
192             key: controlLabels.expanded || 'Expanded', 
193             metaKey: 'Expanded', 
194             disabled: stacked.style() != 'expand', 
195             style: 'expand' 
196           },
197           {
198             key: controlLabels.stack_percent || 'Stack %', 
199             metaKey: 'Stack_Percent', 
200             disabled: stacked.style() != 'stack_percent', 
201             style: 'stack_percent' 
202           }
203         ];
204
205         controlWidth = (cData.length/3) * 260;
206
207         controlsData = controlsData.filter(function(d) {
208           return cData.indexOf(d.metaKey) !== -1;
209         })
210
211         controls
212           .width( controlWidth )
213           .color(['#444', '#444', '#444']);
214
215         g.select('.nv-controlsWrap')
216             .datum(controlsData)
217             .call(controls);
218
219
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;
224         }
225
226
227         g.select('.nv-controlsWrap')
228             .attr('transform', 'translate(0,' + (-margin.top) +')');
229       }
230
231       //------------------------------------------------------------
232
233
234       wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
235
236       if (rightAlignYAxis) {
237           g.select(".nv-y.nv-axis")
238               .attr("transform", "translate(" + availableWidth + ",0)");
239       }
240
241       //------------------------------------------------------------
242       // Main Chart Component(s)
243
244       //------------------------------------------------------------
245       //Set up interactive layer
246       if (useInteractiveGuideline) {
247         interactiveLayer
248            .width(availableWidth)
249            .height(availableHeight)
250            .margin({left: margin.left, top: margin.top})
251            .svgContainer(container)
252            .xScale(x);
253         wrap.select(".nv-interactive").call(interactiveLayer);
254       }
255
256       stacked
257         .width(availableWidth)
258         .height(availableHeight)
259
260       var stackedWrap = g.select('.nv-stackedWrap')
261           .datum(data);
262
263       stackedWrap.transition().call(stacked);
264
265       //------------------------------------------------------------
266
267
268       //------------------------------------------------------------
269       // Setup Axes
270
271       if (showXAxis) {
272         xAxis
273           .scale(x)
274           .ticks( availableWidth / 100 )
275           .tickSize( -availableHeight, 0);
276
277         g.select('.nv-x.nv-axis')
278             .attr('transform', 'translate(0,' + availableHeight + ')');
279
280         g.select('.nv-x.nv-axis')
281           .transition().duration(0)
282             .call(xAxis);
283       }
284
285       if (showYAxis) {
286         yAxis
287           .scale(y)
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);
292
293         g.select('.nv-y.nv-axis')
294           .transition().duration(0)
295             .call(yAxis);
296       }
297
298       //------------------------------------------------------------
299
300
301       //============================================================
302       // Event Handling/Dispatching (in chart's scope)
303       //------------------------------------------------------------
304
305       stacked.dispatch.on('areaClick.toggle', function(e) {
306         if (data.filter(function(d) { return !d.disabled }).length === 1)
307           data.forEach(function(d) {
308             d.disabled = false;
309           });
310         else
311           data.forEach(function(d,i) {
312             d.disabled = (i != e.seriesIndex);
313           });
314
315         state.disabled = data.map(function(d) { return !!d.disabled });
316         dispatch.stateChange(state);
317
318         chart.update();
319       });
320
321       legend.dispatch.on('stateChange', function(newState) {
322         state.disabled = newState.disabled;
323         dispatch.stateChange(state);
324         chart.update();
325       });
326
327       controls.dispatch.on('legendClick', function(d,i) {
328         if (!d.disabled) return;
329
330         controlsData = controlsData.map(function(s) {
331           s.disabled = true;
332           return s;
333         });
334         d.disabled = false;
335
336         stacked.style(d.style);
337     
338
339         state.style = stacked.style();
340         dispatch.stateChange(state);
341
342         chart.update();
343       });
344
345
346       interactiveLayer.dispatch.on('elementMousemove', function(e) {
347           stacked.clearHighlights();
348           var singlePoint, pointIndex, pointXLocation, allData = [];
349           data
350           .filter(function(series, i) {
351             series.seriesIndex = i;
352             return !series.disabled;
353           })
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));
361
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);
364               allData.push({
365                   key: series.key,
366                   value: tooltipValue,
367                   color: color(series,series.seriesIndex),
368                   stackedValue: point.display
369               });
370           });
371
372           allData.reverse();
373
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))
380                {
381                   indexToHighlight = i;
382                   return;
383                }
384             });
385             if (indexToHighlight != null)
386                allData[indexToHighlight].highlight = true;
387           }
388
389           var xValue = xAxis.tickFormat()(chart.x()(singlePoint,pointIndex));
390
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)
398                   .enabled(tooltips)
399                   .valueFormatter(valueFormatter)
400                   .data(
401                       {
402                         value: xValue,
403                         series: allData
404                       }
405                   )();
406
407           interactiveLayer.renderGuideLine(pointXLocation);
408
409       });
410
411       interactiveLayer.dispatch.on("elementMouseout",function(e) {
412           dispatch.tooltipHide();
413           stacked.clearHighlights();
414       });
415
416
417       dispatch.on('tooltipShow', function(e) {
418         if (tooltips) showTooltip(e, that.parentNode);
419       });
420
421       // Update chart from a state object passed to event handler
422       dispatch.on('changeState', function(e) {
423
424         if (typeof e.disabled !== 'undefined') {
425           data.forEach(function(series,i) {
426             series.disabled = e.disabled[i];
427           });
428
429           state.disabled = e.disabled;
430         }
431
432         if (typeof e.style !== 'undefined') {
433           stacked.style(e.style);
434         }
435
436         chart.update();
437       });
438
439     });
440
441
442     return chart;
443   }
444
445
446   //============================================================
447   // Event Handling/Dispatching (out of chart's scope)
448   //------------------------------------------------------------
449
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
453     /*
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);
456       return false;
457     }
458    */
459
460     e.pos = [e.pos[0] + margin.left, e.pos[1] + margin.top],
461     dispatch.tooltipShow(e);
462   });
463
464   stacked.dispatch.on('tooltipHide', function(e) {
465     dispatch.tooltipHide(e);
466   });
467
468   dispatch.on('tooltipHide', function() {
469     if (tooltips) nv.tooltip.cleanup();
470   });
471
472   //============================================================
473
474
475   //============================================================
476   // Expose Public Variables
477   //------------------------------------------------------------
478
479   // expose chart's sub-components
480   chart.dispatch = dispatch;
481   chart.stacked = stacked;
482   chart.legend = legend;
483   chart.controls = controls;
484   chart.xAxis = xAxis;
485   chart.yAxis = yAxis;
486   chart.interactiveLayer = interactiveLayer;
487
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');
489
490   chart.options = nv.utils.optionsFunc.bind(chart);
491
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;
498     return chart;
499   };
500
501   chart.width = function(_) {
502     if (!arguments.length) return width;
503     width = _;
504     return chart;
505   };
506
507   chart.height = function(_) {
508     if (!arguments.length) return height;
509     height = _;
510     return chart;
511   };
512
513   chart.color = function(_) {
514     if (!arguments.length) return color;
515     color = nv.utils.getColor(_);
516     legend.color(color);
517     stacked.color(color);
518     return chart;
519   };
520
521   chart.showControls = function(_) {
522     if (!arguments.length) return showControls;
523     showControls = _;
524     return chart;
525   };
526
527   chart.showLegend = function(_) {
528     if (!arguments.length) return showLegend;
529     showLegend = _;
530     return chart;
531   };
532
533   chart.showXAxis = function(_) {
534     if (!arguments.length) return showXAxis;
535     showXAxis = _;
536     return chart;
537   };
538
539   chart.showYAxis = function(_) {
540     if (!arguments.length) return showYAxis;
541     showYAxis = _;
542     return chart;
543   };
544
545   chart.rightAlignYAxis = function(_) {
546     if(!arguments.length) return rightAlignYAxis;
547     rightAlignYAxis = _;
548     yAxis.orient( (_) ? 'right' : 'left');
549     return chart;
550   };
551
552   chart.useInteractiveGuideline = function(_) {
553     if(!arguments.length) return useInteractiveGuideline;
554     useInteractiveGuideline = _;
555     if (_ === true) {
556        chart.interactive(false);
557        chart.useVoronoi(false);
558     }
559     return chart;
560   };
561
562   chart.tooltip = function(_) {
563     if (!arguments.length) return tooltip;
564     tooltip = _;
565     return chart;
566   };
567
568   chart.tooltips = function(_) {
569     if (!arguments.length) return tooltips;
570     tooltips = _;
571     return chart;
572   };
573
574   chart.tooltipContent = function(_) {
575     if (!arguments.length) return tooltip;
576     tooltip = _;
577     return chart;
578   };
579
580   chart.state = function(_) {
581     if (!arguments.length) return state;
582     state = _;
583     return chart;
584   };
585
586   chart.defaultState = function(_) {
587     if (!arguments.length) return defaultState;
588     defaultState = _;
589     return chart;
590   };
591
592   chart.noData = function(_) {
593     if (!arguments.length) return noData;
594     noData = _;
595     return chart;
596   };
597
598   chart.transitionDuration = function(_) {
599     if (!arguments.length) return transitionDuration;
600     transitionDuration = _;
601     return chart;
602   };
603
604   chart.controlsData = function(_) {
605     if (!arguments.length) return cData;
606     cData = _;
607     return chart;
608   };
609
610   chart.controlLabels = function(_) {
611     if (!arguments.length) return controlLabels;
612     if (typeof _ !== 'object') return controlLabels;
613     controlLabels = _;
614     return chart;
615   };
616
617   yAxis.setTickFormat = yAxis.tickFormat;
618
619   yAxis.tickFormat = function(_) {
620     if (!arguments.length) return yAxisTickFormat;
621     yAxisTickFormat = _;
622     return yAxis;
623   };
624
625
626   //============================================================
627
628   return chart;
629 }