Initial OpenECOMP Portal commit
[portal.git] / ecomp-portal-BE / war / static / fusion / d3 / js / interactiveLayer.js
diff --git a/ecomp-portal-BE/war/static/fusion/d3/js/interactiveLayer.js b/ecomp-portal-BE/war/static/fusion/d3/js/interactiveLayer.js
new file mode 100644 (file)
index 0000000..4dfb68d
--- /dev/null
@@ -0,0 +1,251 @@
+/* Utility class to handle creation of an interactive layer.
+This places a rectangle on top of the chart. When you mouse move over it, it sends a dispatch
+containing the X-coordinate. It can also render a vertical line where the mouse is located.
+
+dispatch.elementMousemove is the important event to latch onto.  It is fired whenever the mouse moves over
+the rectangle. The dispatch is given one object which contains the mouseX/Y location.
+It also has 'pointXValue', which is the conversion of mouseX to the x-axis scale.
+*/
+nv.interactiveGuideline = function() {
+       "use strict";
+       var tooltip = nv.models.tooltip();
+       //Public settings
+       var width = null
+       , height = null
+    //Please pass in the bounding chart's top and left margins
+    //This is important for calculating the correct mouseX/Y positions.
+       , margin = {left: 0, top: 0}
+       , xScale = d3.scale.linear()
+       , yScale = d3.scale.linear()
+       , dispatch = d3.dispatch('elementMousemove', 'elementMouseout','elementDblclick')
+       , showGuideLine = true
+       , svgContainer = null  
+    //Must pass in the bounding chart's <svg> container.
+    //The mousemove event is attached to this container.
+       ;
+
+       //Private variables
+       var isMSIE = navigator.userAgent.indexOf("MSIE") !== -1  //Check user-agent for Microsoft Internet Explorer.
+       ;
+
+
+       function layer(selection) {
+               selection.each(function(data) {
+                               var container = d3.select(this);
+                               
+                               var availableWidth = (width || 960), availableHeight = (height || 400);
+
+                               var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([data]);
+                               var wrapEnter = wrap.enter()
+                                                               .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
+                                                               
+                               
+                               wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
+                               
+                               if (!svgContainer) {
+                                       return;
+                               }
+
+                function mouseHandler() {
+                      var d3mouse = d3.mouse(this);
+                      var mouseX = d3mouse[0];
+                      var mouseY = d3mouse[1];
+                      var subtractMargin = true;
+                      var mouseOutAnyReason = false;
+                      if (isMSIE) {
+                         /*
+                            D3.js (or maybe SVG.getScreenCTM) has a nasty bug in Internet Explorer 10.
+                            d3.mouse() returns incorrect X,Y mouse coordinates when mouse moving
+                            over a rect in IE 10.
+                            However, d3.event.offsetX/Y also returns the mouse coordinates
+                            relative to the triggering <rect>. So we use offsetX/Y on IE.  
+                         */
+                         mouseX = d3.event.offsetX;
+                         mouseY = d3.event.offsetY;
+
+                         /*
+                            On IE, if you attach a mouse event listener to the <svg> container,
+                            it will actually trigger it for all the child elements (like <path>, <circle>, etc).
+                            When this happens on IE, the offsetX/Y is set to where ever the child element
+                            is located.
+                            As a result, we do NOT need to subtract margins to figure out the mouse X/Y
+                            position under this scenario. Removing the line below *will* cause 
+                            the interactive layer to not work right on IE.
+                         */
+                         if(d3.event.target.tagName !== "svg")
+                            subtractMargin = false;
+
+                         if (d3.event.target.className.baseVal.match("nv-legend"))
+                               mouseOutAnyReason = true;
+                          
+                      }
+
+                      if(subtractMargin) {
+                         mouseX -= margin.left;
+                         mouseY -= margin.top;
+                      }
+
+                      /* If mouseX/Y is outside of the chart's bounds,
+                      trigger a mouseOut event.
+                      */
+                      if (mouseX < 0 || mouseY < 0 
+                        || mouseX > availableWidth || mouseY > availableHeight
+                        || (d3.event.relatedTarget && d3.event.relatedTarget.ownerSVGElement === undefined)
+                        || mouseOutAnyReason
+                        ) 
+                      {
+                               if (isMSIE) {
+                                       if (d3.event.relatedTarget 
+                                               && d3.event.relatedTarget.ownerSVGElement === undefined
+                                               && d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass)) {
+                                               return;
+                                       }
+                               }
+                            dispatch.elementMouseout({
+                               mouseX: mouseX,
+                               mouseY: mouseY
+                            });
+                            layer.renderGuideLine(null); //hide the guideline
+                            return;
+                      }
+                      
+                      var pointXValue = xScale.invert(mouseX);
+                      dispatch.elementMousemove({
+                            mouseX: mouseX,
+                            mouseY: mouseY,
+                            pointXValue: pointXValue
+                      });
+
+                      //If user double clicks the layer, fire a elementDblclick dispatch.
+                      if (d3.event.type === "dblclick") {
+                        dispatch.elementDblclick({
+                            mouseX: mouseX,
+                            mouseY: mouseY,
+                            pointXValue: pointXValue
+                        });
+                      }
+                }
+
+                               svgContainer
+                                     .on("mousemove",mouseHandler, true)
+                                     .on("mouseout" ,mouseHandler,true)
+                      .on("dblclick" ,mouseHandler)
+                                     ;
+
+                                //Draws a vertical guideline at the given X postion.
+                               layer.renderGuideLine = function(x) {
+                                       if (!showGuideLine) return;
+                                       var line = wrap.select(".nv-interactiveGuideLine")
+                                             .selectAll("line")
+                                             .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], String);
+
+                                       line.enter()
+                                               .append("line")
+                                               .attr("class", "nv-guideline")
+                                               .attr("x1", function(d) { return d;})
+                                               .attr("x2", function(d) { return d;})
+                                               .attr("y1", availableHeight)
+                                               .attr("y2",0)
+                                               ;
+                                       line.exit().remove();
+
+                               }
+               });
+       }
+
+       layer.dispatch = dispatch;
+       layer.tooltip = tooltip;
+
+       layer.margin = function(_) {
+           if (!arguments.length) return margin;
+           margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
+           margin.left   = typeof _.left   != 'undefined' ? _.left   : margin.left;
+           return layer;
+    };
+
+       layer.width = function(_) {
+               if (!arguments.length) return width;
+               width = _;
+               return layer;
+       };
+
+       layer.height = function(_) {
+               if (!arguments.length) return height;
+               height = _;
+               return layer;
+       };
+
+       layer.xScale = function(_) {
+               if (!arguments.length) return xScale;
+               xScale = _;
+               return layer;
+       };
+
+       layer.showGuideLine = function(_) {
+               if (!arguments.length) return showGuideLine;
+               showGuideLine = _;
+               return layer;
+       };
+
+       layer.svgContainer = function(_) {
+               if (!arguments.length) return svgContainer;
+               svgContainer = _;
+               return layer;
+       };
+
+
+       return layer;
+};
+
+/* Utility class that uses d3.bisect to find the index in a given array, where a search value can be inserted.
+This is different from normal bisectLeft; this function finds the nearest index to insert the search value.
+
+For instance, lets say your array is [1,2,3,5,10,30], and you search for 28. 
+Normal d3.bisectLeft will return 4, because 28 is inserted after the number 10.  But interactiveBisect will return 5
+because 28 is closer to 30 than 10.
+
+Unit tests can be found in: interactiveBisectTest.html
+
+Has the following known issues:
+   * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if the data points are in random order.
+   * Won't work if there are duplicate x coordinate values.
+*/
+nv.interactiveBisect = function (values, searchVal, xAccessor) {
+         "use strict";
+      if (! values instanceof Array) return null;
+      if (typeof xAccessor !== 'function') xAccessor = function(d,i) { return d.x;}
+
+      var bisect = d3.bisector(xAccessor).left;
+      var index = d3.max([0, bisect(values,searchVal) - 1]);
+      var currentValue = xAccessor(values[index], index);
+      if (typeof currentValue === 'undefined') currentValue = index;
+
+      if (currentValue === searchVal) return index;  //found exact match
+
+      var nextIndex = d3.min([index+1, values.length - 1]);
+      var nextValue = xAccessor(values[nextIndex], nextIndex);
+      if (typeof nextValue === 'undefined') nextValue = nextIndex;
+
+      if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal))
+          return index;
+      else
+          return nextIndex
+};
+
+/*
+Returns the index in the array "values" that is closest to searchVal.
+Only returns an index if searchVal is within some "threshold".
+Otherwise, returns null.
+*/
+nv.nearestValueIndex = function (values, searchVal, threshold) {
+      "use strict";
+      var yDistMax = Infinity, indexToHighlight = null;
+      values.forEach(function(d,i) {
+         var delta = Math.abs(searchVal - d);
+         if ( delta <= yDistMax && delta < threshold) {
+            yDistMax = delta;
+            indexToHighlight = i;
+         }
+      });
+      return indexToHighlight;
+};
\ No newline at end of file