Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / static / fusion / raptor / d3 / js / tooltip.js
1 /* Tooltip rendering model for nvd3 charts.
2 window.nv.models.tooltip is the updated,new way to render tooltips.
3
4 window.nv.tooltip.show is the old tooltip code.
5 window.nv.tooltip.* also has various helper methods.
6 */
7 (function() {
8   "use strict";
9   window.nv.tooltip = {};
10
11   /* Model which can be instantiated to handle tooltip rendering.
12     Example usage: 
13     var tip = nv.models.tooltip().gravity('w').distance(23)
14                 .data(myDataObject);
15
16         tip();    //just invoke the returned function to render tooltip.
17   */
18   window.nv.models.tooltip = function() {
19         var content = null    //HTML contents of the tooltip.  If null, the content is generated via the data variable.
20         ,   data = null     /* Tooltip data. If data is given in the proper format, a consistent tooltip is generated.
21         Format of data:
22         {
23             key: "Date",
24             value: "August 2009", 
25             series: [
26                     {
27                         key: "Series 1",
28                         value: "Value 1",
29                         color: "#000"
30                     },
31                     {
32                         key: "Series 2",
33                         value: "Value 2",
34                         color: "#00f"
35                     }
36             ]
37
38         }
39
40         */
41         ,   gravity = 'w'   //Can be 'n','s','e','w'. Determines how tooltip is positioned.
42         ,   distance = 50   //Distance to offset tooltip from the mouse location.
43         ,   snapDistance = 25   //Tolerance allowed before tooltip is moved from its current position (creates 'snapping' effect)
44         ,   fixedTop = null //If not null, this fixes the top position of the tooltip.
45         ,   classes = null  //Attaches additional CSS classes to the tooltip DIV that is created.
46         ,   chartContainer = null   //Parent DIV, of the SVG Container that holds the chart.
47         ,   tooltipElem = null  //actual DOM element representing the tooltip.
48         ,   position = {left: null, top: null}      //Relative position of the tooltip inside chartContainer.
49         ,   enabled = true  //True -> tooltips are rendered. False -> don't render tooltips.
50         //Generates a unique id when you create a new tooltip() object
51         ,   id = "nvtooltip-" + Math.floor(Math.random() * 100000)
52         ;
53
54         //CSS class to specify whether element should not have mouse events.
55         var  nvPointerEventsClass = "nv-pointer-events-none";
56
57         //Format function for the tooltip values column
58         var valueFormatter = function(d,i) {
59             return d;
60         };
61
62         //Format function for the tooltip header value.
63         var headerFormatter = function(d) {
64             return d;
65         };
66
67         //By default, the tooltip model renders a beautiful table inside a DIV.
68         //You can override this function if a custom tooltip is desired.
69         var contentGenerator = function(d) {
70             if (content != null) return content;
71
72             if (d == null) return '';
73
74             var table = d3.select(document.createElement("table"));
75             var theadEnter = table.selectAll("thead")
76                 .data([d])
77                 .enter().append("thead");
78             theadEnter.append("tr")
79                 .append("td")
80                 .attr("colspan",3)
81                 .append("strong")
82                     .classed("x-value",true)
83                     .html(headerFormatter(d.value));
84
85             var tbodyEnter = table.selectAll("tbody")
86                 .data([d])
87                 .enter().append("tbody");
88             var trowEnter = tbodyEnter.selectAll("tr")
89                 .data(function(p) { return p.series})
90                 .enter()
91                 .append("tr")
92                 .classed("highlight", function(p) { return p.highlight})
93                 ;
94
95             trowEnter.append("td")
96                 .classed("legend-color-guide",true)
97                 .append("div")
98                     .style("background-color", function(p) { return p.color});
99             trowEnter.append("td")
100                 .classed("key",true)
101                 .html(function(p) {return p.key});
102             trowEnter.append("td")
103                 .classed("value",true)
104                 .html(function(p,i) { return valueFormatter(p.value,i) });
105
106
107             trowEnter.selectAll("td").each(function(p) {
108                 if (p.highlight) {
109                     var opacityScale = d3.scale.linear().domain([0,1]).range(["#fff",p.color]);
110                     var opacity = 0.6;
111                     d3.select(this)
112                         .style("border-bottom-color", opacityScale(opacity))
113                         .style("border-top-color", opacityScale(opacity))
114                         ;
115                 }
116             });
117
118             var html = table.node().outerHTML;
119             if (d.footer !== undefined)
120                 html += "<div class='footer'>" + d.footer + "</div>";
121             return html;
122
123         };
124
125         var dataSeriesExists = function(d) {
126             if (d && d.series && d.series.length > 0) return true;
127
128             return false;
129         };
130
131         //In situations where the chart is in a 'viewBox', re-position the tooltip based on how far chart is zoomed.
132         function convertViewBoxRatio() {
133             if (chartContainer) {
134               var svg = d3.select(chartContainer);
135               if (svg.node().tagName !== "svg") {
136                  svg = svg.select("svg");
137               }
138               var viewBox = (svg.node()) ? svg.attr('viewBox') : null;
139               if (viewBox) {
140                 viewBox = viewBox.split(' ');
141                 var ratio = parseInt(svg.style('width')) / viewBox[2];
142                 
143                 position.left = position.left * ratio;
144                 position.top  = position.top * ratio;
145               }
146             }
147         }
148
149         //Creates new tooltip container, or uses existing one on DOM.
150         function getTooltipContainer(newContent) {
151             var body;
152             if (chartContainer)
153                 body = d3.select(chartContainer);
154             else
155                 body = d3.select("body");
156
157             var container = body.select(".nvtooltip");
158             if (container.node() === null) {
159                 //Create new tooltip div if it doesn't exist on DOM.
160                 container = body.append("div")
161                     .attr("class", "nvtooltip " + (classes? classes: "xy-tooltip"))
162                     .attr("id",id)
163                     ;
164             }
165         
166
167             container.node().innerHTML = newContent;
168             container.style("top",0).style("left",0).style("opacity",0);
169             container.selectAll("div, table, td, tr").classed(nvPointerEventsClass,true)
170             container.classed(nvPointerEventsClass,true);
171             return container.node();
172         }
173
174         
175
176         //Draw the tooltip onto the DOM.
177         function nvtooltip() {
178             if (!enabled) return;
179             if (!dataSeriesExists(data)) return;
180
181             convertViewBoxRatio();
182
183             var left = position.left;
184             var top = (fixedTop != null) ? fixedTop : position.top;
185             var container = getTooltipContainer(contentGenerator(data));
186             tooltipElem = container;
187             if (chartContainer) {
188                 var svgComp = chartContainer.getElementsByTagName("svg")[0];
189                 var boundRect = (svgComp) ? svgComp.getBoundingClientRect() : chartContainer.getBoundingClientRect();
190                 var svgOffset = {left:0,top:0};
191                 if (svgComp) {
192                     var svgBound = svgComp.getBoundingClientRect();
193                     var chartBound = chartContainer.getBoundingClientRect();
194                     var svgBoundTop = svgBound.top;
195                     
196                     //Defensive code. Sometimes, svgBoundTop can be a really negative
197                     //  number, like -134254. That's a bug. 
198                     //  If such a number is found, use zero instead. FireFox bug only
199                     if (svgBoundTop < 0) {
200                         var containerBound = chartContainer.getBoundingClientRect();
201                         svgBoundTop = (Math.abs(svgBoundTop) > containerBound.height) ? 0 : svgBoundTop;
202                     } 
203                     svgOffset.top = Math.abs(svgBoundTop - chartBound.top);
204                     svgOffset.left = Math.abs(svgBound.left - chartBound.left);
205                 }
206                 //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
207                 //You need to also add any offset between the <svg> element and its containing <div>
208                 //Finally, add any offset of the containing <div> on the whole page.
209                 left += chartContainer.offsetLeft + svgOffset.left - 2*chartContainer.scrollLeft;
210                 top += chartContainer.offsetTop + svgOffset.top - 2*chartContainer.scrollTop;
211             }
212
213             if (snapDistance && snapDistance > 0) {
214                 top = Math.floor(top/snapDistance) * snapDistance;
215             }
216
217             nv.tooltip.calcTooltipPosition([left,top], gravity, distance, container);
218             return nvtooltip;
219         };
220
221         nvtooltip.nvPointerEventsClass = nvPointerEventsClass;
222         
223         nvtooltip.content = function(_) {
224             if (!arguments.length) return content;
225             content = _;
226             return nvtooltip;
227         };
228
229         //Returns tooltipElem...not able to set it.
230         nvtooltip.tooltipElem = function() {
231             return tooltipElem;
232         };
233
234         nvtooltip.contentGenerator = function(_) {
235             if (!arguments.length) return contentGenerator;
236             if (typeof _ === 'function') {
237                 contentGenerator = _;
238             }
239             return nvtooltip;
240         };
241
242         nvtooltip.data = function(_) {
243             if (!arguments.length) return data;
244             data = _;
245             return nvtooltip;
246         };
247
248         nvtooltip.gravity = function(_) {
249             if (!arguments.length) return gravity;
250             gravity = _;
251             return nvtooltip;
252         };
253
254         nvtooltip.distance = function(_) {
255             if (!arguments.length) return distance;
256             distance = _;
257             return nvtooltip;
258         };
259
260         nvtooltip.snapDistance = function(_) {
261             if (!arguments.length) return snapDistance;
262             snapDistance = _;
263             return nvtooltip;
264         };
265
266         nvtooltip.classes = function(_) {
267             if (!arguments.length) return classes;
268             classes = _;
269             return nvtooltip;
270         };
271
272         nvtooltip.chartContainer = function(_) {
273             if (!arguments.length) return chartContainer;
274             chartContainer = _;
275             return nvtooltip;
276         };
277
278         nvtooltip.position = function(_) {
279             if (!arguments.length) return position;
280             position.left = (typeof _.left !== 'undefined') ? _.left : position.left;
281             position.top = (typeof _.top !== 'undefined') ? _.top : position.top;
282             return nvtooltip;
283         };
284
285         nvtooltip.fixedTop = function(_) {
286             if (!arguments.length) return fixedTop;
287             fixedTop = _;
288             return nvtooltip;
289         };
290
291         nvtooltip.enabled = function(_) {
292             if (!arguments.length) return enabled;
293             enabled = _;
294             return nvtooltip;
295         };
296
297         nvtooltip.valueFormatter = function(_) {
298             if (!arguments.length) return valueFormatter;
299             if (typeof _ === 'function') {
300                 valueFormatter = _;
301             }
302             return nvtooltip;
303         };
304
305         nvtooltip.headerFormatter = function(_) {
306             if (!arguments.length) return headerFormatter;
307             if (typeof _ === 'function') {
308                 headerFormatter = _;
309             }
310             return nvtooltip;
311         };
312
313         //id() is a read-only function. You can't use it to set the id.
314         nvtooltip.id = function() {
315             return id;
316         };
317
318
319         return nvtooltip;
320   };
321
322
323   //Original tooltip.show function. Kept for backward compatibility.
324   // pos = [left,top]
325   nv.tooltip.show = function(pos, content, gravity, dist, parentContainer, classes) {
326       
327         //Create new tooltip div if it doesn't exist on DOM.
328         var   container = document.createElement('div');
329         container.className = 'nvtooltip ' + (classes ? classes : 'xy-tooltip');
330
331         var body = parentContainer;
332         if ( !parentContainer || parentContainer.tagName.match(/g|svg/i)) {
333             //If the parent element is an SVG element, place tooltip in the <body> element.
334             body = document.getElementsByTagName('body')[0];
335         }
336    
337         container.style.left = 0;
338         container.style.top = 0;
339         container.style.opacity = 0;
340         container.innerHTML = content;
341         body.appendChild(container);
342         
343         //If the parent container is an overflow <div> with scrollbars, subtract the scroll offsets.
344         if (parentContainer) {
345            pos[0] = pos[0] - parentContainer.scrollLeft;
346            pos[1] = pos[1] - parentContainer.scrollTop;
347         }
348         nv.tooltip.calcTooltipPosition(pos, gravity, dist, container);
349   };
350
351   //Looks up the ancestry of a DOM element, and returns the first NON-svg node.
352   nv.tooltip.findFirstNonSVGParent = function(Elem) {
353             while(Elem.tagName.match(/^g|svg$/i) !== null) {
354                 Elem = Elem.parentNode;
355             }
356             return Elem;
357   };
358
359   //Finds the total offsetTop of a given DOM element.
360   //Looks up the entire ancestry of an element, up to the first relatively positioned element.
361   nv.tooltip.findTotalOffsetTop = function ( Elem, initialTop ) {
362                 var offsetTop = initialTop;
363                 
364                 do {
365                     if( !isNaN( Elem.offsetTop ) ) {
366                         offsetTop += (Elem.offsetTop);
367                     }
368                 } while( Elem = Elem.offsetParent );
369                 return offsetTop;
370   };
371
372   //Finds the total offsetLeft of a given DOM element.
373   //Looks up the entire ancestry of an element, up to the first relatively positioned element.
374   nv.tooltip.findTotalOffsetLeft = function ( Elem, initialLeft) {
375                 var offsetLeft = initialLeft;
376                 
377                 do {
378                     if( !isNaN( Elem.offsetLeft ) ) {
379                         offsetLeft += (Elem.offsetLeft);
380                     }
381                 } while( Elem = Elem.offsetParent );
382                 return offsetLeft;
383   };
384
385   //Global utility function to render a tooltip on the DOM.
386   //pos = [left,top] coordinates of where to place the tooltip, relative to the SVG chart container.
387   //gravity = how to orient the tooltip
388   //dist = how far away from the mouse to place tooltip
389   //container = tooltip DIV
390   nv.tooltip.calcTooltipPosition = function(pos, gravity, dist, container) {
391
392             var height = parseInt(container.offsetHeight),
393                 width = parseInt(container.offsetWidth),
394                 windowWidth = nv.utils.windowSize().width,
395                 windowHeight = nv.utils.windowSize().height,
396                 scrollTop = window.pageYOffset,
397                 scrollLeft = window.pageXOffset,
398                 left, top;
399
400             windowHeight = window.innerWidth >= document.body.scrollWidth ? windowHeight : windowHeight - 16;
401             windowWidth = window.innerHeight >= document.body.scrollHeight ? windowWidth : windowWidth - 16;
402
403             gravity = gravity || 's';
404             dist = dist || 20;
405
406             var tooltipTop = function ( Elem ) {
407                 return nv.tooltip.findTotalOffsetTop(Elem, top);
408             };
409
410             var tooltipLeft = function ( Elem ) {
411                 return nv.tooltip.findTotalOffsetLeft(Elem,left);
412             };
413
414             switch (gravity) {
415               case 'e':
416                 left = pos[0] - width - dist;
417                 top = pos[1] - (height / 2);
418                 var tLeft = tooltipLeft(container);
419                 var tTop = tooltipTop(container);
420                 if (tLeft < scrollLeft) left = pos[0] + dist > scrollLeft ? pos[0] + dist : scrollLeft - tLeft + left;
421                 if (tTop < scrollTop) top = scrollTop - tTop + top;
422                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
423                 break;
424               case 'w':
425                 left = pos[0] + dist;
426                 top = pos[1] - (height / 2);
427                 var tLeft = tooltipLeft(container);
428                 var tTop = tooltipTop(container);
429                 if (tLeft + width > windowWidth) left = pos[0] - width - dist;
430                 if (tTop < scrollTop) top = scrollTop + 5;
431                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
432                 break;
433               case 'n':
434                 left = pos[0] - (width / 2) - 5;
435                 top = pos[1] + dist;
436                 var tLeft = tooltipLeft(container);
437                 var tTop = tooltipTop(container);
438                 if (tLeft < scrollLeft) left = scrollLeft + 5;
439                 if (tLeft + width > windowWidth) left = left - width/2 + 5;
440                 if (tTop + height > scrollTop + windowHeight) top = scrollTop + windowHeight - tTop + top - height;
441                 break;
442               case 's':
443                 left = pos[0] - (width / 2);
444                 top = pos[1] - height - dist;
445                 var tLeft = tooltipLeft(container);
446                 var tTop = tooltipTop(container);
447                 if (tLeft < scrollLeft) left = scrollLeft + 5;
448                 if (tLeft + width > windowWidth) left = left - width/2 + 5;
449                 if (scrollTop > tTop) top = scrollTop;
450                 break;
451               case 'none':
452                 left = pos[0];
453                 top = pos[1] - dist;
454                 var tLeft = tooltipLeft(container);
455                 var tTop = tooltipTop(container);
456                 break;
457             }
458
459
460             container.style.left = left+'px';
461             container.style.top = top+'px';
462             container.style.opacity = 1;
463             container.style.position = 'absolute'; 
464
465             return container;
466     };
467
468     //Global utility function to remove tooltips from the DOM.
469     nv.tooltip.cleanup = function() {
470
471               // Find the tooltips, mark them for removal by this class (so others cleanups won't find it)
472               var tooltips = document.getElementsByClassName('nvtooltip');
473               var purging = [];
474               while(tooltips.length) {
475                 purging.push(tooltips[0]);
476                 tooltips[0].style.transitionDelay = '0 !important';
477                 tooltips[0].style.opacity = 0;
478                 tooltips[0].className = 'nvtooltip-pending-removal';
479               }
480
481               setTimeout(function() {
482
483                   while (purging.length) {
484                      var removeMe = purging.pop();
485                       removeMe.parentNode.removeChild(removeMe);
486                   }
487             }, 500);
488     };
489
490 })();