Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 78 additions & 95 deletions src/hammer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {callback as call} from 'chart.js/helpers';
import Hammer from 'hammerjs';
import {doPan, doZoom} from './core';
import {getEnabledScalesByPoint} from './utils';
Expand All @@ -10,137 +11,118 @@ function createEnabler(chart, panOptions) {
if (!event || !event.srcEvent) { // Sometimes Hammer queries this with a null event.
return true;
}
const requireModifier = panOptions.modifierKey
&& (event.pointerType === 'mouse');
const requireModifier = panOptions.modifierKey && (event.pointerType === 'mouse');
if (requireModifier && !event.srcEvent[panOptions.modifierKey + 'Key']) {
if (typeof panOptions.onPanRejected === 'function') {
panOptions.onPanRejected({
chart: chart,
event: event
});
}
call(panOptions.onPanRejected, [{chart, event}]);
return false;
}
return true;
};
}

export function startHammer(chart, options) {
const node = chart.canvas;
const {pan: panOptions, zoom: zoomOptions} = options;
function pinchAxes(p0, p1) {
// fingers position difference
const x = Math.abs(p0.clientX - p1.clientX);
const y = Math.abs(p0.clientY - p1.clientY);

const mc = new Hammer.Manager(node);
if (zoomOptions && zoomOptions.enabled) {
mc.add(new Hammer.Pinch());
}
if (panOptions && panOptions.enabled) {
mc.add(new Hammer.Pan({
threshold: panOptions.threshold,
enable: createEnabler(chart, panOptions)
}));
}
// diagonal fingers will change both (xy) axes
const p = x / y;
return p > 0.3 && p < 1.7 ? 'xy' : x > y ? 'x' : 'y';
}

function createPinchHandlers(chart, zoomOptions) {
// Hammer reports the total scaling. We need the incremental amount
let currentPinchScaling;
const handlePinch = function(e) {
const diff = 1 / (currentPinchScaling) * e.scale;
const {center, pointers} = e;
const zoom = 1 / (currentPinchScaling) * e.scale;
const rect = e.target.getBoundingClientRect();
const offsetX = e.center.x - rect.left;
const offsetY = e.center.y - rect.top;
const center = {
x: offsetX,
y: offsetY
const focalPoint = {
x: center.x - rect.left,
y: center.y - rect.top
};

// fingers position difference
const x = Math.abs(e.pointers[0].clientX - e.pointers[1].clientX);
const y = Math.abs(e.pointers[0].clientY - e.pointers[1].clientY);

// diagonal fingers will change both (xy) axes
const p = x / y;
let xy;
if (p > 0.3 && p < 1.7) {
xy = 'xy';
} else if (x > y) {
xy = 'x'; // x axis
} else {
xy = 'y'; // y axis
}
const xy = pinchAxes(pointers[0], pointers[1]);

doZoom(chart, diff, diff, center, zoomOptions, xy);

if (typeof zoomOptions.onZoom === 'function') {
zoomOptions.onZoom({chart: chart});
}
doZoom(chart, zoom, zoom, focalPoint, zoomOptions, xy);

// Keep track of overall scale
currentPinchScaling = e.scale;
};

mc.on('pinchstart', function() {
currentPinchScaling = 1; // reset tracker
});
mc.on('pinch', handlePinch);
mc.on('pinchend', function(e) {
handlePinch(e);
currentPinchScaling = null; // reset
if (typeof zoomOptions.onZoomComplete === 'function') {
zoomOptions.onZoomComplete({chart: chart});
return {
start() {
currentPinchScaling = 1;
},
pinch: handlePinch,
end(e) {
handlePinch(e);
currentPinchScaling = null; // reset
call(zoomOptions.onZoomComplete, [chart]);
}
});
};
}

let currentDeltaX = null;
let currentDeltaY = null;
let panning = false;
let panningScales = null;
function createPanHandlers(chart, panOptions) {
let delta = null;
let enabledScales = null;
const handlePan = function(e) {
if (currentDeltaX !== null && currentDeltaY !== null) {
panning = true;
const deltaX = e.deltaX - currentDeltaX;
const deltaY = e.deltaY - currentDeltaY;
currentDeltaX = e.deltaX;
currentDeltaY = e.deltaY;
doPan(chart, deltaX, deltaY, panOptions, panningScales);
if (delta !== null) {
chart.panning = true;
doPan(chart, e.deltaX - delta.x, e.deltaY - delta.y, panOptions, enabledScales);
delta = {x: e.deltaX, y: e.deltaY};
}
};

mc.on('panstart', function(e) {
if (panOptions.enabled) {
return {
start(e) {
const rect = e.target.getBoundingClientRect();
const x = e.center.x - rect.left;
const y = e.center.y - rect.top;
panningScales = getEnabledScalesByPoint(panOptions, x, y, chart);
enabledScales = getEnabledScalesByPoint(panOptions, x, y, chart);

delta = {x: 0, y: 0};
handlePan(e);
},
move: handlePan,
end() {
delta = null;
setTimeout(() => (chart.panning = false), 500);
call(panOptions.onPanComplete, [chart]);
}
};
}

currentDeltaX = 0;
currentDeltaY = 0;
handlePan(e);
});
mc.on('panmove', handlePan);
mc.on('panend', function() {
currentDeltaX = null;
currentDeltaY = null;
setTimeout(function() {
panning = false;
}, 500);
if (typeof panOptions.onPanComplete === 'function') {
panOptions.onPanComplete({chart: chart});
}
});
const hammers = new WeakMap();
export function startHammer(chart, options) {
const canvas = chart.canvas;
const {pan: panOptions, zoom: zoomOptions} = options;

chart.$zoom._ghostClickHandler = function(e) {
if (panning && e.cancelable) {
e.stopImmediatePropagation();
e.preventDefault();
}
};
node.addEventListener('click', chart.$zoom._ghostClickHandler);
const mc = new Hammer.Manager(canvas);
if (zoomOptions && zoomOptions.enabled) {
const {start, pinch, end} = createPinchHandlers(chart, zoomOptions);

mc.add(new Hammer.Pinch());
mc.on('pinchstart', start);
mc.on('pinch', pinch);
mc.on('pinchend', end);
}

if (panOptions && panOptions.enabled) {
const {start, move, end} = createPanHandlers(chart, panOptions);
mc.add(new Hammer.Pan({
threshold: panOptions.threshold,
enable: createEnabler(chart, panOptions)
}));
mc.on('panstart', start);
mc.on('panmove', move);
mc.on('panend', end);
}

chart._mc = mc;
hammers.set(chart, mc);
}

export function stopHammer(chart) {
const mc = chart._mc;
const mc = hammers.get(chart);
if (mc) {
mc.remove('pinchstart');
mc.remove('pinch');
Expand All @@ -149,5 +131,6 @@ export function stopHammer(chart) {
mc.remove('pan');
mc.remove('panend');
mc.destroy();
hammers.delete(chart);
}
}
7 changes: 7 additions & 0 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ export default {
chart.resetZoom = () => resetZoom(chart);
},

beforeEvent(chart, args) {
if (args.event.type === 'click' && chart.panning) {
// cancel the click event at pan end
return false;
}
},

beforeUpdate: function(chart, args, options) {
addListeners(chart, options);
},
Expand Down