Skip to content
118 changes: 96 additions & 22 deletions src/dygraph-interaction-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,15 @@ DygraphInteraction.endZoom = function(event, g, context) {
context.dragStartY = null;
};

// isOutOfExtremes: checks that number p is out of extremes ex.
// ex is a tuple [a, b], with a <= b
function isOutOfExtremes(p, ex) {

return p < ex[0] || p > ex[1]

// note on ex[0] < ex[1]: sometimes extremes are invalid,
// ex[0] must be less than or equal to ex[1]
}
/**
* @private
*/
Expand All @@ -418,17 +427,40 @@ DygraphInteraction.startTouch = function(event, g, context) {
var touches = [];
for (var i = 0; i < event.touches.length; i++) {
var t = event.touches[i];
var rect = t.target.getBoundingClientRect()
// 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)
dataX: g.toDataXCoord(t.clientX-rect.left),
dataY: g.toDataYCoord(t.clientY-rect.top)
// identifier: t.identifier
});
}
context.initialTouches = touches;

var pinchCenter
context.pinchOutOfExtremes = false
if (touches.length >= 2) {

// only screen coordinates can be averaged (data coords could be log scale).
pinchCenter = {
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)
};

var xExtremes = g.xAxisExtremes()
var yExtremes = g.yAxisExtremes()
if (xExtremes[0] >= xExtremes[1] || isOutOfExtremes(pinchCenter.dataX, xExtremes))
context.pinchOutOfExtremes = true
if (yExtremes.find(yEx => yEx[0] >= yEx[1] || isOutOfExtremes(pinchCenter.dataY, yEx)))
context.pinchOutOfExtremes = true
}

if (touches.length == 1) {
// This is just a swipe.
context.initialPinchCenter = touches[0];
Expand All @@ -437,15 +469,7 @@ DygraphInteraction.startTouch = function(event, g, context) {
// 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)
};
context.initialPinchCenter = pinchCenter

// Make pinches in a 45-degree swath around either axis 1-dimensional zooms.
var initialAngle = 180 / Math.PI * Math.atan2(
Expand All @@ -457,8 +481,11 @@ DygraphInteraction.startTouch = function(event, g, context) {
if (initialAngle > 90) initialAngle = 90 - initialAngle;

context.touchDirections = {
x: (initialAngle < (90 - 45/2)),
y: (initialAngle > 45/2)
//TODO: it does not seem to work well
//x: (initialAngle < (90 - 45/2)),
//y: (initialAngle > 45/2)
x: true,
y: true
};
}

Expand Down Expand Up @@ -509,32 +536,62 @@ DygraphInteraction.moveTouch = function(event, g, context) {
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;

var xExtremes = g.xAxisExtremes()
var yExtremes = g.yAxisExtremes()

var xScale = 1.0, yScale = 1.0;

// 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) {
if (touches.length >= 2 && !context.pinchOutOfExtremes) {
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;
var minAllowed = 5

if (Math.abs(initHalfWidth) > minAllowed) {
// sensitiveness dampening: smaller pinches count much less
var damp = 1 / (Math.abs(initHalfWidth)-minAllowed)

var nowHalfWidth = touches[1].pageX - c_now.pageX
xScale = (nowHalfWidth + damp) / (initHalfWidth + damp);
}

if (Math.abs(initHalfHeight) > minAllowed) {
var damp = 1 / (Math.abs(initHalfHeight) - minAllowed)

var nowHalfHeight = touches[1].pageY - c_now.pageY
yScale = (nowHalfHeight + damp) / (initHalfHeight + damp);
}
}

// 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) {
var oldDateWindow = g.dateWindow_ || context.initialRange.x
g.dateWindow_ = [
c_init.dataX - swipe.dataX / xScale + (context.initialRange.x[0] - c_init.dataX) / xScale,
c_init.dataX - swipe.dataX / xScale + (context.initialRange.x[1] - c_init.dataX) / xScale
];

if (xExtremes[0] < xExtremes[1]) {
var pef = g.getNumericOption("panEdgeFraction") || 1/10
var a = xExtremes[0] - (xExtremes[1] - xExtremes[0]) * pef
var b = xExtremes[1] + (xExtremes[1] - xExtremes[0]) * pef
if (g.dateWindow_[0] < a) {
g.dateWindow_[0] = a
if (xScale == 1) // if it is a pan, do not scale the window
g.dateWindow_[1] = oldDateWindow[1]
}
if (g.dateWindow_[1] > b) {
g.dateWindow_[1] = b
if (xScale == 1)
g.dateWindow_[0] = oldDateWindow[0]
}
}
didZoom = true;
}

Expand All @@ -545,10 +602,27 @@ DygraphInteraction.moveTouch = function(event, g, context) {
if (logscale) {
// TODO(danvk): implement
} else {
var oldValueRange = axis.valueRange || context.initialRange.y
axis.valueRange = [
c_init.dataY - swipe.dataY / yScale + (context.initialRange.y[0] - c_init.dataY) / yScale,
c_init.dataY - swipe.dataY / yScale + (context.initialRange.y[1] - c_init.dataY) / yScale
];
if (yExtremes[i][0] < yExtremes[i][1]) {
var pef = g.getNumericOption("panEdgeFraction") || 1/10
var a = yExtremes[i][0] - (yExtremes[i][1] - yExtremes[i][0]) * pef
var b = yExtremes[i][1] + (yExtremes[i][1] - yExtremes[i][0]) * pef
if (axis.valueRange[0] < a) {
axis.valueRange[0] = a
if (xScale == 1) // if it is a pan, do not scale
axis.valueRange[1] = oldValueRange[1]
}
if (axis.valueRange[1] > b) {
axis.valueRange[1] = b
if (xScale == 1)
axis.valueRange[0] = oldValueRange[0]
}
}

didZoom = true;
}
}
Expand Down