X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=dygraph-interaction-model.js;h=2af345c7dcf7f4f7fe18d9871dc1db4900bc76af;hb=ad617f174a6b4ec2aea3d06553534dcaf278a2bb;hp=9b7feb5cc62b43deb8d68265972f04115e3a5735;hpb=e26b71566419e1c051f3fbd1f4f8f64b063a04c9;p=dygraphs.git diff --git a/dygraph-interaction-model.js b/dygraph-interaction-model.js index 9b7feb5..2af345c 100644 --- a/dygraph-interaction-model.js +++ b/dygraph-interaction-model.js @@ -28,10 +28,11 @@ Dygraph.Interaction = {}; * 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. + * @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; @@ -62,8 +63,8 @@ Dygraph.Interaction.startPan = function(event, g, context) { var boundedTopY = g.toDomYCoord(yExtremes[0], i) + maxYPixelsToDraw; var boundedBottomY = g.toDomYCoord(yExtremes[1], i) - maxYPixelsToDraw; - var boundedTopValue = g.toDataYCoord(boundedTopY); - var boundedBottomValue = g.toDataYCoord(boundedBottomY); + var boundedTopValue = g.toDataYCoord(boundedTopY, i); + var boundedBottomValue = g.toDataYCoord(boundedBottomY, i); boundedValues[i] = [boundedTopValue, boundedBottomValue]; } @@ -83,7 +84,8 @@ Dygraph.Interaction.startPan = function(event, g, context) { var yRange = g.yAxisRange(i); // TODO(konigsberg): These values should be in |context|. // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale. - if (axis.logscale) { + 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 { @@ -106,10 +108,11 @@ Dygraph.Interaction.startPan = function(event, g, context) { * 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. + * @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); @@ -133,12 +136,13 @@ Dygraph.Interaction.movePan = function(event, g, context) { // 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 pixelsDragged = context.dragEndY - context.dragStartY; var unitsDragged = pixelsDragged * axis_data.unitsPerPixel; var boundedValue = context.boundedValues ? context.boundedValues[i] : null; @@ -156,7 +160,8 @@ Dygraph.Interaction.movePan = function(event, g, context) { minValue = maxValue - axis_data.dragValueRange; } } - if (axis.logscale) { + var logscale = g.attributes_.getForAxis("logscale", i); + if (logscale) { axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue), Math.pow(Dygraph.LOG_SCALE, maxValue) ]; } else { @@ -176,10 +181,11 @@ Dygraph.Interaction.movePan = function(event, g, context) { * 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. + * @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); @@ -213,13 +219,15 @@ Dygraph.Interaction.endPan = function(event, g, context) { * 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. + * @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; }; /** @@ -230,12 +238,14 @@ Dygraph.Interaction.startZoom = function(event, g, context) { * 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. + * @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); @@ -260,13 +270,19 @@ Dygraph.Interaction.moveZoom = function(event, g, context) { 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. + // 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; @@ -309,10 +325,11 @@ Dygraph.Interaction.treatMouseOpAsClick = function(g, event, context) { * 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. + * @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; @@ -326,16 +343,30 @@ Dygraph.Interaction.endZoom = function(event, g, context) { 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) { - g.doZoomX_(Math.min(context.dragStartX, context.dragEndX), - Math.max(context.dragStartX, context.dragEndX)); + 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) { - g.doZoomY_(Math.min(context.dragStartY, context.dragEndY), - Math.max(context.dragStartY, context.dragEndY)); + 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 { - g.clearZoomRect_(); + if (context.zoomMoved) g.clearZoomRect_(); } context.dragStartX = null; context.dragStartY = null; @@ -346,6 +377,11 @@ Dygraph.Interaction.endZoom = function(event, g, context) { */ 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]; @@ -364,8 +400,9 @@ Dygraph.Interaction.startTouch = function(event, g, context) { // This is just a swipe. context.initialPinchCenter = touches[0]; context.touchDirections = { x: true, y: true }; - } else if (touches.length == 2) { + } 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 = { @@ -403,6 +440,9 @@ Dygraph.Interaction.startTouch = function(event, g, context) { * @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]; @@ -443,7 +483,7 @@ Dygraph.Interaction.moveTouch = function(event, g, context) { if (touches.length == 1) { xScale = 1.0; yScale = 1.0; - } else if (touches.length == 2) { + } else if (touches.length >= 2) { var initHalfWidth = (initialTouches[1].pageX - c_init.pageX); xScale = (touches[1].pageX - c_now.pageX) / initHalfWidth; @@ -455,37 +495,63 @@ Dygraph.Interaction.moveTouch = function(event, g, context) { 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]; - if (axis.logscale) { + 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) { + 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; + } } }; @@ -545,6 +611,7 @@ Dygraph.Interaction.defaultModel = { if (context.isZooming) { context.dragEndX = null; context.dragEndY = null; + g.clearZoomRect_(); } }, @@ -557,9 +624,7 @@ Dygraph.Interaction.defaultModel = { if (event.altKey || event.shiftKey) { return; } - // TODO(konigsberg): replace g.doUnzoom()_ with something that is - // friendlier to public use. - g.doUnzoom_(); + g.resetZoom(); } };