Skip to content

Commit

Permalink
Internal state handling (#457)
Browse files Browse the repository at this point in the history
* Internal state handling

* More refactoring

* Add tolerance

* Remove unused functions
  • Loading branch information
kurkle authored Apr 17, 2021
1 parent b7bd3e2 commit 15c443d
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 209 deletions.
80 changes: 37 additions & 43 deletions src/core.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import {each, callback as call} from 'chart.js/helpers';
import {panFunctions, zoomFunctions} from './scale.types';
import {getState} from './state';
import {directionEnabled, getEnabledScalesByPoint} from './utils';

function storeOriginalOptions(chart) {
const originalOptions = chart.$zoom._originalOptions;
function storeOriginalScaleLimits(chart) {
const {originalScaleLimits} = getState(chart);
each(chart.scales, function(scale) {
if (!originalOptions[scale.id]) {
originalOptions[scale.id] = {min: scale.options.min, max: scale.options.max};
if (!originalScaleLimits[scale.id]) {
originalScaleLimits[scale.id] = {min: scale.options.min, max: scale.options.max};
}
});
each(originalOptions, function(opt, key) {
each(originalScaleLimits, function(opt, key) {
if (!chart.scales[key]) {
delete originalOptions[key];
delete originalScaleLimits[key];
}
});
return originalScaleLimits;
}

function zoomScale(scale, zoom, center, zoomOptions) {
Expand All @@ -31,56 +33,50 @@ function zoomScale(scale, zoom, center, zoomOptions) {
* @param {boolean} [useTransition] Whether to use `zoom` transition
*/
export function doZoom(chart, percentZoomX, percentZoomY, focalPoint, zoomOptions, whichAxes, useTransition) {
const ca = chart.chartArea;
if (!focalPoint) {
const ca = chart.chartArea;
focalPoint = {
x: (ca.left + ca.right) / 2,
y: (ca.top + ca.bottom) / 2,
};
}

if (zoomOptions.enabled) {
storeOriginalOptions(chart);
// Do the zoom here
const zoomMode = typeof zoomOptions.mode === 'function' ? zoomOptions.mode({chart: chart}) : zoomOptions.mode;
storeOriginalScaleLimits(chart);
// Do the zoom here
const zoomMode = typeof zoomOptions.mode === 'function' ? zoomOptions.mode({chart: chart}) : zoomOptions.mode;

// Which axes should be modified when fingers were used.
let _whichAxes;
if (zoomMode === 'xy' && whichAxes !== undefined) {
// based on fingers positions
_whichAxes = whichAxes;
} else {
// no effect
_whichAxes = 'xy';
}

// Which axes should be modified when fingers were used.
let _whichAxes;
if (zoomMode === 'xy' && whichAxes !== undefined) {
// based on fingers positions
_whichAxes = whichAxes;
} else {
// no effect
_whichAxes = 'xy';
const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint.x, focalPoint.y, chart);
each(enabledScales || chart.scales, function(scale) {
if (scale.isHorizontal() && directionEnabled(zoomMode, 'x', chart) && directionEnabled(_whichAxes, 'x', chart)) {
zoomScale(scale, percentZoomX, focalPoint, zoomOptions);
} else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y', chart) && directionEnabled(_whichAxes, 'y', chart)) {
zoomScale(scale, percentZoomY, focalPoint, zoomOptions);
}
});

const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint.x, focalPoint.y, chart);
each(enabledScales || chart.scales, function(scale) {
if (scale.isHorizontal() && directionEnabled(zoomMode, 'x', chart) && directionEnabled(_whichAxes, 'x', chart)) {
zoomOptions.scaleAxes = 'x';
zoomScale(scale, percentZoomX, focalPoint, zoomOptions);
} else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y', chart) && directionEnabled(_whichAxes, 'y', chart)) {
// Do Y zoom
zoomOptions.scaleAxes = 'y';
zoomScale(scale, percentZoomY, focalPoint, zoomOptions);
}
});

chart.update(useTransition ? 'zoom' : 'none');
chart.update(useTransition ? 'zoom' : 'none');

call(zoomOptions.onZoom, [chart]);
}
call(zoomOptions.onZoom, [chart]);
}

export function resetZoom(chart) {
storeOriginalOptions(chart);
const originalOptions = chart.$zoom._originalOptions;
each(chart.scales, function(scale) {
const originalScaleLimits = storeOriginalScaleLimits(chart);

each(chart.scales, function(scale) {
const scaleOptions = scale.options;
if (originalOptions[scale.id]) {
scaleOptions.min = originalOptions[scale.id].min;
scaleOptions.max = originalOptions[scale.id].max;
if (originalScaleLimits[scale.id]) {
scaleOptions.min = originalScaleLimits[scale.id].min;
scaleOptions.max = originalScaleLimits[scale.id].max;
} else {
delete scaleOptions.min;
delete scaleOptions.max;
Expand All @@ -95,16 +91,14 @@ function panScale(scale, delta, panOptions) {
}

export function doPan(chart, deltaX, deltaY, panOptions, panningScales) {
storeOriginalOptions(chart);
storeOriginalScaleLimits(chart);
if (panOptions.enabled) {
const panMode = typeof panOptions.mode === 'function' ? panOptions.mode({chart}) : panOptions.mode;

each(panningScales || chart.scales, function(scale) {
if (scale.isHorizontal() && directionEnabled(panMode, 'x', chart) && deltaX !== 0) {
panOptions.scaleAxes = 'x';
panScale(scale, deltaX, panOptions);
} else if (!scale.isHorizontal() && directionEnabled(panMode, 'y', chart) && deltaY !== 0) {
panOptions.scaleAxes = 'y';
panScale(scale, deltaY, panOptions);
}
});
Expand Down
124 changes: 65 additions & 59 deletions src/hammer.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import {callback as call} from 'chart.js/helpers';
import Hammer from 'hammerjs';
import {doPan, doZoom} from './core';
import {getState} from './state';
import {getEnabledScalesByPoint} from './utils';

function createEnabler(chart, panOptions) {
function createEnabler(chart) {
const state = getState(chart);
return function(recognizer, event) {
const panOptions = state.options.pan;
if (!panOptions || !panOptions.enabled) {
return false;
}
if (!event || !event.srcEvent) { // Sometimes Hammer queries this with a null event.
return true;
}
const requireModifier = panOptions.modifierKey && (event.pointerType === 'mouse');
if (requireModifier && !event.srcEvent[panOptions.modifierKey + 'Key']) {
const modifierKey = panOptions.modifierKey;
const requireModifier = modifierKey && (event.pointerType === 'mouse');
if (requireModifier && !event.srcEvent[modifierKey + 'Key']) {
call(panOptions.onPanRejected, [{chart, event}]);
return false;
}
Expand All @@ -30,12 +34,11 @@ function pinchAxes(p0, p1) {
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) {
function handlePinch(chart, state, e) {
if (state.scale) {
const {center, pointers} = e;
const zoom = 1 / (currentPinchScaling) * e.scale;
// Hammer reports the total scaling. We need the incremental amount
const zoom = 1 / state.scale * e.scale;
const rect = e.target.getBoundingClientRect();
const focalPoint = {
x: center.x - rect.left,
Expand All @@ -44,78 +47,81 @@ function createPinchHandlers(chart, zoomOptions) {

const xy = pinchAxes(pointers[0], pointers[1]);

doZoom(chart, zoom, zoom, focalPoint, zoomOptions, xy);
doZoom(chart, zoom, zoom, focalPoint, state.options.zoom, xy);

// Keep track of overall scale
currentPinchScaling = e.scale;
};
return {
start() {
currentPinchScaling = 1;
},
pinch: handlePinch,
end(e) {
handlePinch(e);
currentPinchScaling = null; // reset
call(zoomOptions.onZoomComplete, [chart]);
}
};
state.scale = e.scale;
}
}

function createPanHandlers(chart, panOptions) {
let delta = null;
let enabledScales = null;
const handlePan = function(e) {
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};
}
};
function startPinch(chart, state) {
if (state.options.zoom.enabled) {
state.scale = 1;
}
}

return {
start(e) {
const rect = e.target.getBoundingClientRect();
const x = e.center.x - rect.left;
const y = e.center.y - rect.top;
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]);
}
};
function endPinch(chart, state, e) {
if (state.scale) {
handlePinch(chart, state, e);
state.scale = null; // reset
call(state.options.zoom.onZoomComplete, [chart]);
}
}


function handlePan(chart, state, e) {
const delta = state.delta;
if (delta !== null) {
state.panning = true;
doPan(chart, e.deltaX - delta.x, e.deltaY - delta.y, state.options.pan, state.panScales);
state.delta = {x: e.deltaX, y: e.deltaY};
}
}

function startPan(chart, state, e) {
const panOptions = state.options.pan;
if (!panOptions.enabled) {
return;
}
const rect = e.target.getBoundingClientRect();
const x = e.center.x - rect.left;
const y = e.center.y - rect.top;

state.panScales = getEnabledScalesByPoint(panOptions, x, y, chart);
state.delta = {x: 0, y: 0};
handlePan(chart, state, e);
}

function endPan(chart, state) {
state.delta = null;
if (state.panning) {
setTimeout(() => (state.panning = false), 500);
call(state.options.pan.onPanComplete, [chart]);
}
}

const hammers = new WeakMap();
export function startHammer(chart, options) {
const state = getState(chart);
const canvas = chart.canvas;
const {pan: panOptions, zoom: zoomOptions} = options;

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);
mc.on('pinchstart', () => startPinch(chart, state));
mc.on('pinch', (e) => handlePinch(chart, state, e));
mc.on('pinchend', (e) => endPinch(chart, state, e));
}

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

hammers.set(chart, mc);
Expand Down
Loading

0 comments on commit 15c443d

Please sign in to comment.