Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / static / fusion / d3 / js / interactiveLayer.js
1 /* Utility class to handle creation of an interactive layer.
2 This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
3 containing the X-coordinate. It can also render a vertical line where the mouse is located.
4
5 dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
6 the rectangle. The dispatch is given one object which contains the mouseX/Y location.
7 It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
8 */
9 nv.interactiveGuideline = function() {
10         "use strict";
11         var tooltip = nv.models.tooltip();
12         //Public settings
13         var width = null
14         , height = null
15     //Please pass in the bounding chart's top and left margins
16     //This is important for calculating the correct mouseX/Y positions.
17         , margin = {left: 0, top: 0}
18         , xScale = d3.scale.linear()
19         , yScale = d3.scale.linear()
20         , dispatch = d3.dispatch('elementMousemove', 'elementMouseout','elementDblclick')
21         , showGuideLine = true
22         , svgContainer = null  
23     //Must pass in the bounding chart's <svg> container.
24     //The mousemove event is attached to this container.
25         ;
26
27         //Private variables
28         var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1  //Check user-agent for Microsoft Internet Explorer.
29         ;
30
31
32         function layer(selection) {
33                 selection.each(function(data) {
34                                 var container = d3.select(this);
35                                 
36                                 var availableWidth = (width || 960), availableHeight = (height || 400);
37
38                                 var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]);
39                                 var wrapEnter = wrap.enter()
40                                                                 .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
41                                                                 
42                                 
43                                 wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
44                                 
45                                 if (!svgContainer) {
46                                         return;
47                                 }
48
49                 function mouseHandler() {
50                       var d3mouse = d3.mouse(this);
51                       var mouseX = d3mouse[0];
52                       var mouseY = d3mouse[1];
53                       var subtractMargin = true;
54                       var mouseOutAnyReason = false;
55                       if (isMSIE) {
56                          /*
57                             D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
58                             d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
59                             over a rect in IE 10.
60                             However, d3.event.offsetX/Y also returns the mouse coordinates
61                             relative to the triggering <rect>. So we use offsetX/Y on IE.  
62                          */
63                          mouseX = d3.event.offsetX;
64                          mouseY = d3.event.offsetY;
65
66                          /*
67                             On IE, if you attach a mouse event listener to the <svg> container,
68                             it will actually trigger it for all the child elements (like <path>, <circle>, etc).
69                             When this happens on IE, the offsetX/Y is set to where ever the child element
70                             is located.
71                             As a result, we do NOT need to subtract margins to figure out the mouse X/Y
72                             position under this scenario. Removing the line below *will* cause 
73                             the interactive layer to not work right on IE.
74                          */
75                          if(d3.event.target.tagName !== "svg")
76                             subtractMargin = false;
77
78                          if (d3.event.target.className.baseVal.match("nv-legend"))
79                                 mouseOutAnyReason = true;
80                           
81                       }
82
83                       if(subtractMargin) {
84                          mouseX -= margin.left;
85                          mouseY -= margin.top;
86                       }
87
88                       /* If mouseX/Y is outside of the chart's bounds,
89                       trigger a mouseOut event.
90                       */
91                       if (mouseX < 0 || mouseY < 0 
92                         || mouseX > availableWidth || mouseY > availableHeight
93                         || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
94                         || mouseOutAnyReason
95                         ) 
96                       {
97                                 if (isMSIE) {
98                                         if (d3.event.relatedTarget 
99                                                 && d3.event.relatedTarget.ownerSVGElement === undefined
100                                                 && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
101                                                 return;
102                                         }
103                                 }
104                             dispatch.elementMouseout({
105                                mouseX: mouseX,
106                                mouseY: mouseY
107                             });
108                             layer.renderGuideLine(null); //hide the guideline
109                             return;
110                       }
111                       
112                       var pointXValue = xScale.invert(mouseX);
113                       dispatch.elementMousemove({
114                             mouseX: mouseX,
115                             mouseY: mouseY,
116                             pointXValue: pointXValue
117                       });
118
119                       //If user double clicks the layer, fire a elementDblclick dispatch.
120                       if (d3.event.type === "dblclick") {
121                         dispatch.elementDblclick({
122                             mouseX: mouseX,
123                             mouseY: mouseY,
124                             pointXValue: pointXValue
125                         });
126                       }
127                 }
128
129                                 svgContainer
130                                       .on("mousemove",mouseHandler, true)
131                                       .on("mouseout" ,mouseHandler,true)
132                       .on("dblclick" ,mouseHandler)
133                                       ;
134
135                                  //Draws a vertical guideline at the given X postion.
136                                 layer.renderGuideLine = function(x) {
137                                         if (!showGuideLine) return;
138                                         var line = wrap.select(".nv-interactiveGuideLine")
139                                               .selectAll("line")
140                                               .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
141
142                                         line.enter()
143                                                 .append("line")
144                                                 .attr("class", "nv-guideline")
145                                                 .attr("x1", function(d) { return d;})
146                                                 .attr("x2", function(d) { return d;})
147                                                 .attr("y1", availableHeight)
148                                                 .attr("y2",0)
149                                                 ;
150                                         line.exit().remove();
151
152                                 }
153                 });
154         }
155
156         layer.dispatch = dispatch;
157         layer.tooltip = tooltip;
158
159         layer.margin = function(_) {
160             if (!arguments.length) return margin;
161             margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
162             margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
163             return layer;
164     };
165
166         layer.width = function(_) {
167                 if (!arguments.length) return width;
168                 width = _;
169                 return layer;
170         };
171
172         layer.height = function(_) {
173                 if (!arguments.length) return height;
174                 height = _;
175                 return layer;
176         };
177
178         layer.xScale = function(_) {
179                 if (!arguments.length) return xScale;
180                 xScale = _;
181                 return layer;
182         };
183
184         layer.showGuideLine = function(_) {
185                 if (!arguments.length) return showGuideLine;
186                 showGuideLine = _;
187                 return layer;
188         };
189
190         layer.svgContainer = function(_) {
191                 if (!arguments.length) return svgContainer;
192                 svgContainer = _;
193                 return layer;
194         };
195
196
197         return layer;
198 };
199
200 /* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
201 This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
202
203 For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. 
204 Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
205 because 28 is closer to 30 than 10.
206
207 Unit tests can be found in: interactiveBisectTest.html
208
209 Has the following known issues:
210    * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
211    * Won't work if there are duplicate x coordinate values.
212 */
213 nv.interactiveBisect = function (values, searchVal, xAccessor) {
214           "use strict";
215       if (! values instanceof Array) return null;
216       if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;}
217
218       var bisect = d3.bisector(xAccessor).left;
219       var index = d3.max([0, bisect(values,searchVal) - 1]);
220       var currentValue = xAccessor(values[index], index);
221       if (typeof currentValue === 'undefined') currentValue = index;
222
223       if (currentValue === searchVal) return index;  //found exact match
224
225       var nextIndex = d3.min([index+1, values.length - 1]);
226       var nextValue = xAccessor(values[nextIndex], nextIndex);
227       if (typeof nextValue === 'undefined') nextValue = nextIndex;
228
229       if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal))
230           return index;
231       else
232           return nextIndex
233 };
234
235 /*
236 Returns the index in the array "values" that is closest to searchVal.
237 Only returns an index if searchVal is within some "threshold".
238 Otherwise, returns null.
239 */
240 nv.nearestValueIndex = function (values, searchVal, threshold) {
241       "use strict";
242       var yDistMax = Infinity, indexToHighlight = null;
243       values.forEach(function(d,i) {
244          var delta = Math.abs(searchVal - d);
245          if ( delta <= yDistMax && delta < threshold) {
246             yDistMax = delta;
247             indexToHighlight = i;
248          }
249       });
250       return indexToHighlight;
251 };