From 91d04c64771832a0b8815ffbe1f0f9920320d94d Mon Sep 17 00:00:00 2001 From: Pamela Dragosh Date: Tue, 14 Feb 2017 19:41:00 -0500 Subject: Initial OpenECOMP policy/engine commit Change-Id: I7dbff37733b661643dd4d1caefa3d7dccc361b6e Signed-off-by: Pamela Dragosh --- .../raptor/dy3/js/dygraph-interaction-model.js | 676 +++++++++++++++++++++ 1 file changed, 676 insertions(+) create mode 100644 ecomp-sdk-app/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js (limited to 'ecomp-sdk-app/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js') diff --git a/ecomp-sdk-app/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js b/ecomp-sdk-app/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js new file mode 100644 index 000000000..2af345c7d --- /dev/null +++ b/ecomp-sdk-app/src/main/webapp/static/fusion/raptor/dy3/js/dygraph-interaction-model.js @@ -0,0 +1,676 @@ +/** + * @license + * Copyright 2011 Robert Konigsberg (konigsberg@google.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/** + * @fileoverview The default interaction model for Dygraphs. This is kept out + * of dygraph.js for better navigability. + * @author Robert Konigsberg (konigsberg@google.com) + */ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + +/** + * A collection of functions to facilitate build custom interaction models. + * @class + */ +Dygraph.Interaction = {}; + +/** + * Called in response to an interaction model operation that + * should start the default panning behavior. + * + * It's used in the default callback for "mousedown" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the startPan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.startPan = function(event, g, context) { + var i, axis; + context.isPanning = true; + var xRange = g.xAxisRange(); + context.dateRange = xRange[1] - xRange[0]; + context.initialLeftmostDate = xRange[0]; + context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1); + + if (g.attr_("panEdgeFraction")) { + var maxXPixelsToDraw = g.width_ * g.attr_("panEdgeFraction"); + var xExtremes = g.xAxisExtremes(); // I REALLY WANT TO CALL THIS xTremes! + + var boundedLeftX = g.toDomXCoord(xExtremes[0]) - maxXPixelsToDraw; + var boundedRightX = g.toDomXCoord(xExtremes[1]) + maxXPixelsToDraw; + + var boundedLeftDate = g.toDataXCoord(boundedLeftX); + var boundedRightDate = g.toDataXCoord(boundedRightX); + context.boundedDates = [boundedLeftDate, boundedRightDate]; + + var boundedValues = []; + var maxYPixelsToDraw = g.height_ * g.attr_("panEdgeFraction"); + + for (i = 0; i < g.axes_.length; i++) { + axis = g.axes_[i]; + var yExtremes = axis.extremeRange; + + var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; + var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; + + var boundedTopValue = g.toDataYCoord(boundedTopY, i); + var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); + + boundedValues[i] = [boundedTopValue, boundedBottomValue]; + } + context.boundedValues = boundedValues; + } + + // Record the range of each y-axis at the start of the drag. + // If any axis has a valueRange or valueWindow, then we want a 2D pan. + // We can't store data directly in g.axes_, because it does not belong to us + // and could change out from under us during a pan (say if there's a data + // update). + context.is2DPan = false; + context.axes = []; + for (i = 0; i < g.axes_.length; i++) { + axis = g.axes_[i]; + var axis_data = {}; + var yRange = g.yAxisRange(i); + // TODO(konigsberg): These values should be in |context|. + // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + axis_data.initialTopValue = Dygraph.log10(yRange[1]); + axis_data.dragValueRange = Dygraph.log10(yRange[1]) - Dygraph.log10(yRange[0]); + } else { + axis_data.initialTopValue = yRange[1]; + axis_data.dragValueRange = yRange[1] - yRange[0]; + } + axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1); + context.axes.push(axis_data); + + // While calculating axes, set 2dpan. + if (axis.valueWindow || axis.valueRange) context.is2DPan = true; + } +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that pans the view. + * + * It's used in the default callback for "mousemove" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the movePan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.movePan = function(event, g, context) { + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + + var minDate = context.initialLeftmostDate - + (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel; + if (context.boundedDates) { + minDate = Math.max(minDate, context.boundedDates[0]); + } + var maxDate = minDate + context.dateRange; + if (context.boundedDates) { + if (maxDate > context.boundedDates[1]) { + // Adjust minDate, and recompute maxDate. + minDate = minDate - (maxDate - context.boundedDates[1]); + maxDate = minDate + context.dateRange; + } + } + + g.dateWindow_ = [minDate, maxDate]; + + // y-axis scaling is automatic unless this is a full 2D pan. + if (context.is2DPan) { + + var pixelsDragged = context.dragEndY - context.dragStartY; + + // Adjust each axis appropriately. + for (var i = 0; i < g.axes_.length; i++) { + var axis = g.axes_[i]; + var axis_data = context.axes[i]; + var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; + + var boundedValue = context.boundedValues ? context.boundedValues[i] : null; + + // In log scale, maxValue and minValue are the logs of those values. + var maxValue = axis_data.initialTopValue + unitsDragged; + if (boundedValue) { + maxValue = Math.min(maxValue, boundedValue[1]); + } + var minValue = maxValue - axis_data.dragValueRange; + if (boundedValue) { + if (minValue < boundedValue[0]) { + // Adjust maxValue, and recompute minValue. + maxValue = maxValue - (minValue - boundedValue[0]); + minValue = maxValue - axis_data.dragValueRange; + } + } + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue), + Math.pow(Dygraph.LOG_SCALE, maxValue) ]; + } else { + axis.valueWindow = [ minValue, maxValue ]; + } + } + } + + g.drawGraph_(false); +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that ends panning. + * + * It's used in the default callback for "mouseup" operations. + * Custom interaction model builders can use it to provide the default + * panning behavior. + * + * @param {Event} event the event object which led to the endPan call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.endPan = function(event, g, context) { + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && + g.lastx_ !== undefined && g.lastx_ != -1) { + Dygraph.Interaction.treatMouseOpAsClick(g, event, context); + } + + // TODO(konigsberg): mouseup should just delete the + // context object, and mousedown should create a new one. + context.isPanning = false; + context.is2DPan = false; + context.initialLeftmostDate = null; + context.dateRange = null; + context.valueRange = null; + context.boundedDates = null; + context.boundedValues = null; + context.axes = null; +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that starts zooming. + * + * It's used in the default callback for "mousedown" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the startZoom call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.startZoom = function(event, g, context) { + context.isZooming = true; + context.zoomMoved = false; +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that defines zoom boundaries. + * + * It's used in the default callback for "mousemove" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the moveZoom call. + * @param {Dygraph} g The dygraph on which to act. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.moveZoom = function(event, g, context) { + context.zoomMoved = true; + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + + var xDelta = Math.abs(context.dragStartX - context.dragEndX); + var yDelta = Math.abs(context.dragStartY - context.dragEndY); + + // drag direction threshold for y axis is twice as large as x axis + context.dragDirection = (xDelta < yDelta / 2) ? Dygraph.VERTICAL : Dygraph.HORIZONTAL; + + g.drawZoomRect_( + context.dragDirection, + context.dragStartX, + context.dragEndX, + context.dragStartY, + context.dragEndY, + context.prevDragDirection, + context.prevEndX, + context.prevEndY); + + context.prevEndX = context.dragEndX; + context.prevEndY = context.dragEndY; + context.prevDragDirection = context.dragDirection; +}; + +/** + * @param {Dygraph} g + * @param {Event} event + * @param {Object} context + */ +Dygraph.Interaction.treatMouseOpAsClick = function(g, event, context) { + var clickCallback = g.attr_('clickCallback'); + var pointClickCallback = g.attr_('pointClickCallback'); + + var selectedPoint = null; + + // Find out if the click occurs on a point. This only matters if there's a + // pointClickCallback. + if (pointClickCallback) { + var closestIdx = -1; + var closestDistance = Number.MAX_VALUE; + + // check if the click was on a particular point. + for (var i = 0; i < g.selPoints_.length; i++) { + var p = g.selPoints_[i]; + var distance = Math.pow(p.canvasx - context.dragEndX, 2) + + Math.pow(p.canvasy - context.dragEndY, 2); + if (!isNaN(distance) && + (closestIdx == -1 || distance < closestDistance)) { + closestDistance = distance; + closestIdx = i; + } + } + + // Allow any click within two pixels of the dot. + var radius = g.attr_('highlightCircleSize') + 2; + if (closestDistance <= radius * radius) { + selectedPoint = g.selPoints_[closestIdx]; + } + } + + if (selectedPoint) { + pointClickCallback(event, selectedPoint); + } + + // TODO(danvk): pass along more info about the points, e.g. 'x' + if (clickCallback) { + clickCallback(event, g.lastx_, g.selPoints_); + } +}; + +/** + * Called in response to an interaction model operation that + * responds to an event that performs a zoom based on previously defined + * bounds.. + * + * It's used in the default callback for "mouseup" operations. + * Custom interaction model builders can use it to provide the default + * zooming behavior. + * + * @param {Event} event the event object which led to the endZoom call. + * @param {Dygraph} g The dygraph on which to end the zoom. + * @param {Object} context The dragging context object (with + * dragStartX/dragStartY/etc. properties). This function modifies the + * context. + */ +Dygraph.Interaction.endZoom = function(event, g, context) { + context.isZooming = false; + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && + g.lastx_ !== undefined && g.lastx_ != -1) { + Dygraph.Interaction.treatMouseOpAsClick(g, event, context); + } + + // The zoom rectangle is visibly clipped to the plot area, so its behavior + // should be as well. + // See http://code.google.com/p/dygraphs/issues/detail?id=280 + var plotArea = g.getArea(); + if (regionWidth >= 10 && context.dragDirection == Dygraph.HORIZONTAL) { + var left = Math.min(context.dragStartX, context.dragEndX), + right = Math.max(context.dragStartX, context.dragEndX); + left = Math.max(left, plotArea.x); + right = Math.min(right, plotArea.x + plotArea.w); + if (left < right) { + g.doZoomX_(left, right); + } + context.cancelNextDblclick = true; + } else if (regionHeight >= 10 && context.dragDirection == Dygraph.VERTICAL) { + var top = Math.min(context.dragStartY, context.dragEndY), + bottom = Math.max(context.dragStartY, context.dragEndY); + top = Math.max(top, plotArea.y); + bottom = Math.min(bottom, plotArea.y + plotArea.h); + if (top < bottom) { + g.doZoomY_(top, bottom); + } + context.cancelNextDblclick = true; + } else { + if (context.zoomMoved) g.clearZoomRect_(); + } + context.dragStartX = null; + context.dragStartY = null; +}; + +/** + * @private + */ +Dygraph.Interaction.startTouch = function(event, g, context) { + event.preventDefault(); // touch browsers are all nice. + if (event.touches.length > 1) { + // If the user ever puts two fingers down, it's not a double tap. + context.startTimeForDoubleTapMs = null; + } + + var touches = []; + for (var i = 0; i < event.touches.length; i++) { + var t = event.touches[i]; + // we dispense with 'dragGetX_' because all touchBrowsers support pageX + touches.push({ + pageX: t.pageX, + pageY: t.pageY, + dataX: g.toDataXCoord(t.pageX), + dataY: g.toDataYCoord(t.pageY) + // identifier: t.identifier + }); + } + context.initialTouches = touches; + + if (touches.length == 1) { + // This is just a swipe. + context.initialPinchCenter = touches[0]; + context.touchDirections = { x: true, y: true }; + } else if (touches.length >= 2) { + // It's become a pinch! + // In case there are 3+ touches, we ignore all but the "first" two. + + // only screen coordinates can be averaged (data coords could be log scale). + context.initialPinchCenter = { + pageX: 0.5 * (touches[0].pageX + touches[1].pageX), + pageY: 0.5 * (touches[0].pageY + touches[1].pageY), + + // TODO(danvk): remove + dataX: 0.5 * (touches[0].dataX + touches[1].dataX), + dataY: 0.5 * (touches[0].dataY + touches[1].dataY) + }; + + // Make pinches in a 45-degree swath around either axis 1-dimensional zooms. + var initialAngle = 180 / Math.PI * Math.atan2( + context.initialPinchCenter.pageY - touches[0].pageY, + touches[0].pageX - context.initialPinchCenter.pageX); + + // use symmetry to get it into the first quadrant. + initialAngle = Math.abs(initialAngle); + if (initialAngle > 90) initialAngle = 90 - initialAngle; + + context.touchDirections = { + x: (initialAngle < (90 - 45/2)), + y: (initialAngle > 45/2) + }; + } + + // save the full x & y ranges. + context.initialRange = { + x: g.xAxisRange(), + y: g.yAxisRange() + }; +}; + +/** + * @private + */ +Dygraph.Interaction.moveTouch = function(event, g, context) { + // If the tap moves, then it's definitely not part of a double-tap. + context.startTimeForDoubleTapMs = null; + + var i, touches = []; + for (i = 0; i < event.touches.length; i++) { + var t = event.touches[i]; + touches.push({ + pageX: t.pageX, + pageY: t.pageY + }); + } + var initialTouches = context.initialTouches; + + var c_now; + + // old and new centers. + var c_init = context.initialPinchCenter; + if (touches.length == 1) { + c_now = touches[0]; + } else { + c_now = { + pageX: 0.5 * (touches[0].pageX + touches[1].pageX), + pageY: 0.5 * (touches[0].pageY + touches[1].pageY) + }; + } + + // this is the "swipe" component + // we toss it out for now, but could use it in the future. + var swipe = { + pageX: c_now.pageX - c_init.pageX, + pageY: c_now.pageY - c_init.pageY + }; + var dataWidth = context.initialRange.x[1] - context.initialRange.x[0]; + var dataHeight = context.initialRange.y[0] - context.initialRange.y[1]; + swipe.dataX = (swipe.pageX / g.plotter_.area.w) * dataWidth; + swipe.dataY = (swipe.pageY / g.plotter_.area.h) * dataHeight; + var xScale, yScale; + + // The residual bits are usually split into scale & rotate bits, but we split + // them into x-scale and y-scale bits. + if (touches.length == 1) { + xScale = 1.0; + yScale = 1.0; + } else if (touches.length >= 2) { + var initHalfWidth = (initialTouches[1].pageX - c_init.pageX); + xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; + + var initHalfHeight = (initialTouches[1].pageY - c_init.pageY); + yScale = (touches[1].pageY - c_now.pageY) / initHalfHeight; + } + + // Clip scaling to [1/8, 8] to prevent too much blowup. + xScale = Math.min(8, Math.max(0.125, xScale)); + yScale = Math.min(8, Math.max(0.125, yScale)); + + var didZoom = false; + if (context.touchDirections.x) { + g.dateWindow_ = [ + c_init.dataX - swipe.dataX + (context.initialRange.x[0] - c_init.dataX) / xScale, + c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale + ]; + didZoom = true; + } + + if (context.touchDirections.y) { + for (i = 0; i < 1 /*g.axes_.length*/; i++) { + var axis = g.axes_[i]; + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { + // TODO(danvk): implement + } else { + axis.valueWindow = [ + c_init.dataY - swipe.dataY + (context.initialRange.y[0] - c_init.dataY) / yScale, + c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale + ]; + didZoom = true; + } + } + } + + g.drawGraph_(false); + + // We only call zoomCallback on zooms, not pans, to mirror desktop behavior. + if (didZoom && touches.length > 1 && g.attr_('zoomCallback')) { + var viewWindow = g.xAxisRange(); + g.attr_("zoomCallback")(viewWindow[0], viewWindow[1], g.yAxisRanges()); + } +}; + +/** + * @private + */ +Dygraph.Interaction.endTouch = function(event, g, context) { + if (event.touches.length !== 0) { + // this is effectively a "reset" + Dygraph.Interaction.startTouch(event, g, context); + } else if (event.changedTouches.length == 1) { + // Could be part of a "double tap" + // The heuristic here is that it's a double-tap if the two touchend events + // occur within 500ms and within a 50x50 pixel box. + var now = new Date().getTime(); + var t = event.changedTouches[0]; + if (context.startTimeForDoubleTapMs && + now - context.startTimeForDoubleTapMs < 500 && + context.doubleTapX && Math.abs(context.doubleTapX - t.screenX) < 50 && + context.doubleTapY && Math.abs(context.doubleTapY - t.screenY) < 50) { + g.resetZoom(); + } else { + context.startTimeForDoubleTapMs = now; + context.doubleTapX = t.screenX; + context.doubleTapY = t.screenY; + } + } +}; + +/** + * Default interation model for dygraphs. You can refer to specific elements of + * this when constructing your own interaction model, e.g.: + * g.updateOptions( { + * interactionModel: { + * mousedown: Dygraph.defaultInteractionModel.mousedown + * } + * } ); + */ +Dygraph.Interaction.defaultModel = { + // Track the beginning of drag events + mousedown: function(event, g, context) { + // Right-click should not initiate a zoom. + if (event.button && event.button == 2) return; + + context.initializeMouseDown(event, g, context); + + if (event.altKey || event.shiftKey) { + Dygraph.startPan(event, g, context); + } else { + Dygraph.startZoom(event, g, context); + } + }, + + // Draw zoom rectangles when the mouse is down and the user moves around + mousemove: function(event, g, context) { + if (context.isZooming) { + Dygraph.moveZoom(event, g, context); + } else if (context.isPanning) { + Dygraph.movePan(event, g, context); + } + }, + + mouseup: function(event, g, context) { + if (context.isZooming) { + Dygraph.endZoom(event, g, context); + } else if (context.isPanning) { + Dygraph.endPan(event, g, context); + } + }, + + touchstart: function(event, g, context) { + Dygraph.Interaction.startTouch(event, g, context); + }, + touchmove: function(event, g, context) { + Dygraph.Interaction.moveTouch(event, g, context); + }, + touchend: function(event, g, context) { + Dygraph.Interaction.endTouch(event, g, context); + }, + + // Temporarily cancel the dragging event when the mouse leaves the graph + mouseout: function(event, g, context) { + if (context.isZooming) { + context.dragEndX = null; + context.dragEndY = null; + g.clearZoomRect_(); + } + }, + + // Disable zooming out if panning. + dblclick: function(event, g, context) { + if (context.cancelNextDblclick) { + context.cancelNextDblclick = false; + return; + } + if (event.altKey || event.shiftKey) { + return; + } + g.resetZoom(); + } +}; + +Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.Interaction.defaultModel; + +// old ways of accessing these methods/properties +Dygraph.defaultInteractionModel = Dygraph.Interaction.defaultModel; +Dygraph.endZoom = Dygraph.Interaction.endZoom; +Dygraph.moveZoom = Dygraph.Interaction.moveZoom; +Dygraph.startZoom = Dygraph.Interaction.startZoom; +Dygraph.endPan = Dygraph.Interaction.endPan; +Dygraph.movePan = Dygraph.Interaction.movePan; +Dygraph.startPan = Dygraph.Interaction.startPan; + +Dygraph.Interaction.nonInteractiveModel_ = { + mousedown: function(event, g, context) { + context.initializeMouseDown(event, g, context); + }, + mouseup: function(event, g, context) { + // TODO(danvk): this logic is repeated in Dygraph.Interaction.endZoom + context.dragEndX = g.dragGetX_(event, context); + context.dragEndY = g.dragGetY_(event, context); + var regionWidth = Math.abs(context.dragEndX - context.dragStartX); + var regionHeight = Math.abs(context.dragEndY - context.dragStartY); + + if (regionWidth < 2 && regionHeight < 2 && + g.lastx_ !== undefined && g.lastx_ != -1) { + Dygraph.Interaction.treatMouseOpAsClick(g, event, context); + } + } +}; + +// Default interaction model when using the range selector. +Dygraph.Interaction.dragIsPanInteractionModel = { + mousedown: function(event, g, context) { + context.initializeMouseDown(event, g, context); + Dygraph.startPan(event, g, context); + }, + mousemove: function(event, g, context) { + if (context.isPanning) { + Dygraph.movePan(event, g, context); + } + }, + mouseup: function(event, g, context) { + if (context.isPanning) { + Dygraph.endPan(event, g, context); + } + } +}; -- cgit 1.2.3-korg