Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pinch zoom ux #991

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
125 changes: 100 additions & 25 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,31 +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 + (context.initialRange.x[0] - c_init.dataX) / xScale,
c_init.dataX - swipe.dataX + (context.initialRange.x[1] - c_init.dataX) / xScale
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 @@ -544,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 + (context.initialRange.y[0] - c_init.dataY) / yScale,
c_init.dataY - swipe.dataY + (context.initialRange.y[1] - c_init.dataY) / yScale
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