Skip to content

Enable touch interactions for select/lasso/others #1804

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

Merged
merged 17 commits into from
Jul 10, 2017
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
"gl-shader": "4.2.0",
"gl-spikes2d": "^1.0.1",
"gl-surface3d": "^1.3.0",
"has-hover": "^1.0.0",
"mapbox-gl": "^0.22.0",
"matrix-camera-controller": "^2.1.3",
"mouse-change": "^1.4.0",
Expand Down
80 changes: 63 additions & 17 deletions src/components/dragelement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

'use strict';

var mouseOffset = require('mouse-event-offset');
var hasHover = require('has-hover');

var Plotly = require('../../plotly');
var Lib = require('../../lib');

Expand Down Expand Up @@ -61,18 +64,29 @@ dragElement.init = function init(options) {
startX,
startY,
newMouseDownTime,
cursor,
dragCover,
initialTarget;

if(!gd._mouseDownTime) gd._mouseDownTime = 0;

options.element.style.pointerEvents = 'all';

if(hasHover) {
options.element.onmousedown = onStart;
}
else {
options.element.ontouchstart = onStart;
}

function onStart(e) {
// make dragging and dragged into properties of gd
// so that others can look at and modify them
gd._dragged = false;
gd._dragging = true;
startX = e.clientX;
startY = e.clientY;
var offset = pointerOffset(e);
startX = offset[0];
startY = offset[1];
initialTarget = e.target;

newMouseDownTime = (new Date()).getTime();
Expand All @@ -88,20 +102,33 @@ dragElement.init = function init(options) {

if(options.prepFn) options.prepFn(e, startX, startY);

dragCover = coverSlip();
if(hasHover) {
dragCover = coverSlip();
dragCover.addEventListener('mousemove', onMove);
dragCover.addEventListener('mouseup', onDone);
dragCover.addEventListener('mouseout', onDone);

dragCover.onmousemove = onMove;
dragCover.onmouseup = onDone;
dragCover.onmouseout = onDone;
dragCover.style.cursor = window.getComputedStyle(options.element).cursor;
}

// document acts as a dragcover for mobile, bc we can't create dragcover dynamically
else {
dragCover = document;
document.addEventListener('touchmove', onMove);
document.addEventListener('touchend', onDone);

cursor = window.getComputedStyle(document.documentElement).cursor;
document.documentElement.style.cursor = window.getComputedStyle(options.element).cursor;
}

dragCover.style.cursor = window.getComputedStyle(options.element).cursor;

return Lib.pauseEvent(e);
}

function onMove(e) {
var dx = e.clientX - startX,
dy = e.clientY - startY,
var offset = pointerOffset(e),
dx = offset[0] - startX,
dy = offset[1] - startY,
minDrag = options.minDrag || constants.MINDRAG;

if(Math.abs(dx) < minDrag) dx = 0;
Expand All @@ -117,10 +144,24 @@ dragElement.init = function init(options) {
}

function onDone(e) {
dragCover.onmousemove = null;
dragCover.onmouseup = null;
dragCover.onmouseout = null;
Lib.removeElement(dragCover);
if(hasHover) {
dragCover.removeEventListener('mousemove', onMove);
dragCover.removeEventListener('mouseup', onDone);
dragCover.removeEventListener('mouseout', onDone);

Lib.removeElement(dragCover);
}

else {
dragCover.removeEventListener('touchmove', onMove);
dragCover.removeEventListener('touchend', onDone);

if(cursor) {
dragCover.documentElement.style.cursor = cursor;
cursor = null;
}
}


if(!gd._dragging) {
gd._dragged = false;
Expand All @@ -143,12 +184,13 @@ dragElement.init = function init(options) {
e2 = new MouseEvent('click', e);
}
catch(err) {
var offset = pointerOffset(e);
e2 = document.createEvent('MouseEvents');
e2.initMouseEvent('click',
e.bubbles, e.cancelable,
e.view, e.detail,
e.screenX, e.screenY,
e.clientX, e.clientY,
offset[0], offset[1],
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
e.button, e.relatedTarget);
}
Expand All @@ -162,9 +204,6 @@ dragElement.init = function init(options) {

return Lib.pauseEvent(e);
}

options.element.onmousedown = onStart;
options.element.style.pointerEvents = 'all';
};

function coverSlip() {
Expand All @@ -191,3 +230,10 @@ function finishDrag(gd) {
gd._dragging = false;
if(gd._replotPending) Plotly.plot(gd);
}

function pointerOffset(e) {
return mouseOffset(
e.changedTouches && e.changedTouches[0] || e,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not blocking, but I'd prefer more parentheses in cases like this - it works fine, but takes me too much mental effort to work out the operator precedence while reading it.

document.body
);
}
6 changes: 6 additions & 0 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var hasHover = require('has-hover');

var Plotly = require('../plotly');
var Lib = require('../lib');
Expand Down Expand Up @@ -423,6 +424,11 @@ function setPlotContext(gd, config) {
context.showLink = false;
context.displayModeBar = false;
}

// make sure hover-only devices have mode bar visible
if(context.displayModeBar === 'hover' && !hasHover) {
context.displayModeBar = true;
}
}

function plotPolar(gd, data, layout) {
Expand Down
22 changes: 20 additions & 2 deletions src/plots/gl2d/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

var mouseChange = require('mouse-change');
var mouseWheel = require('mouse-wheel');
var mouseOffset = require('mouse-event-offset');
var cartesianConstants = require('../cartesian/constants');

module.exports = createCamera;
Expand Down Expand Up @@ -55,7 +56,24 @@ function createCamera(scene) {
return false;
}

result.mouseListener = mouseChange(element, function(buttons, x, y) {
result.mouseListener = mouseChange(element, handleInteraction);

// enable simple touch interactions
element.addEventListener('touchstart', function(ev) {
var xy = mouseOffset(ev.changedTouches[0], element);
handleInteraction(0, xy[0], xy[1]);
handleInteraction(1, xy[0], xy[1]);
});
element.addEventListener('touchmove', function(ev) {
ev.preventDefault();
var xy = mouseOffset(ev.changedTouches[0], element);
handleInteraction(1, xy[0], xy[1]);
});
element.addEventListener('touchend', function() {
handleInteraction(0, result.lastPos[0], result.lastPos[1]);
});

function handleInteraction(buttons, x, y) {
var dataBox = scene.calcDataBox(),
viewBox = plot.viewBox;

Expand Down Expand Up @@ -235,7 +253,7 @@ function createCamera(scene) {

result.lastPos[0] = x;
result.lastPos[1] = y;
});
}

result.wheelListener = mouseWheel(element, function(dx, dy) {
var dataBox = scene.calcDataBox(),
Expand Down
12 changes: 12 additions & 0 deletions src/plots/gl2d/scene2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -525,11 +525,23 @@ proto.updateTraces = function(fullData, calcData) {
};

proto.updateFx = function(dragmode) {
// switch to svg interactions in lasso/select mode
if(dragmode === 'lasso' || dragmode === 'select') {
this.mouseContainer.style['pointer-events'] = 'none';
} else {
this.mouseContainer.style['pointer-events'] = 'auto';
}

// set proper cursor
if(dragmode === 'pan') {
this.mouseContainer.style.cursor = 'move';
}
else if(dragmode === 'zoom') {
this.mouseContainer.style.cursor = 'crosshair';
}
else {
this.mouseContainer.style.cursor = null;
}
};

proto.emitPointAction = function(nextSelection, eventType) {
Expand Down
2 changes: 1 addition & 1 deletion src/traces/scattergl/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ proto.updateFast = function(options) {
this.idToIndex = idToIndex;

// form selected set
if(selection) {
if(selection && selection.length) {
selPositions = new Float64Array(2 * selection.length);

for(i = 0, l = selection.length; i < l; i++) {
Expand Down