Skip to content

Commit

Permalink
Added pinch zoom ux changes from alpt:pinch-zoom ux danvk#991. Work r…
Browse files Browse the repository at this point in the history
…eally well on mobile

touch devices, good job.
  • Loading branch information
Richard Shine committed Apr 13, 2020
1 parent deaf6d8 commit 6a50c5d
Showing 1 changed file with 80 additions and 12 deletions.
92 changes: 80 additions & 12 deletions src/dygraph-interaction-model.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,13 @@ DygraphInteraction.startTouch = function(event, g, context) {
touches.push({
pageX: t.pageX,
pageY: t.pageY,
dataX: g.toDataXCoord(t.clientX-rect.left),
dataY: g.toDataYCoord(t.clientY-rect.top)
dataX: g.toDataXCoord(t.clientX - rect.left),
dataY: g.toDataYCoord(t.clientY - rect.top)
// identifier: t.identifier
});
}
context.initialTouches = touches;
context.pinchOutOfExtremes = false;

if (touches.length == 1) {
// This is possibly a touchOver, save the last touch to check
Expand All @@ -450,6 +451,15 @@ DygraphInteraction.startTouch = function(event, g, context) {
dataY: 0.5 * (touches[0].dataY + touches[1].dataY)
};

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

// 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,
Expand All @@ -460,8 +470,10 @@ DygraphInteraction.startTouch = function(event, g, context) {
if (initialAngle > 90) initialAngle = 90 - initialAngle;

context.touchDirections = {
x: (initialAngle < (90 - 45/2)),
y: (initialAngle > 45/2)
// x: (initialAngle < (90 - 45/2)),
// y: (initialAngle > 45/2)
x: true,
y: true
};
}

Expand Down Expand Up @@ -515,19 +527,30 @@ 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, dampening;

if (Math.abs(initHalfWidth) > minAllowed) {
// sensitivity dampening: smaller pinches count form much less
dampening = 1 / (Math.abs(initHalfWidth) - minAllowed);
var nowHalfWidth = touches[1].pageX - c_now.pageX;
xScale = (nowHalfWidth + dampening) / (initHalfWidth + dampening);
}

if (Math.abs(initialTouches) > minAllowed) {
dampening = 1 / (Math.abs(initHalfHeight) - minAllowed);
var nowHalfHeight = touches[1].pageY - c_now.pageY;
yScale = (nowHalfHeight + dampening) / (initHalfHeight + dampening);
}
}

// Clip scaling to [1/8, 8] to prevent too much blowup.
Expand All @@ -536,10 +559,29 @@ DygraphInteraction.moveTouch = function(event, g, context) {

var didZoom = false;
if (context.touchDirections.x) {
var oldDateWindow = g.dateWindow_;
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 panning, do no scale 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 @@ -550,10 +592,28 @@ DygraphInteraction.moveTouch = function(event, g, context) {
if (logscale) {
// TODO(danvk): implement
} else {
var oldValueRange = axis.valueRange;
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) {
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 Expand Up @@ -636,6 +696,14 @@ var distanceFromChart = function(event, g) {
return Math.max(dx, dy);
};

/**
* isOutOfExtremes checks that number p is out of extremes ex.
* ex is a tuple [a, b], with a <= b
*/
var isOutOfExtremes = function(p, ex) {
return p < ex[0] || p > ex[1];
}

/**
* Default interation model for dygraphs. You can refer to specific elements of
* this when constructing your own interaction model, e.g.:
Expand Down

0 comments on commit 6a50c5d

Please sign in to comment.