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

Refactor to logical entities #452

Merged
merged 7 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
115 changes: 115 additions & 0 deletions src/core.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {each, callback as call} from 'chart.js/helpers';
import {panFunctions, zoomFunctions} from './scale.types';
import {directionEnabled, getEnabledScalesByPoint} from './utils';

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

function zoomScale(scale, zoom, center, zoomOptions) {
call(zoomFunctions[scale.type], [scale, zoom, center, zoomOptions]);
}

/**
* @param chart The chart instance
* @param {number} percentZoomX The zoom percentage in the x direction
* @param {number} percentZoomY The zoom percentage in the y direction
* @param {{x: number, y: number}} focalPoint The x and y coordinates of zoom focal point. The point which doesn't change while zooming. E.g. the location of the mouse cursor when "drag: false"
* @param {object} zoomOptions The zoom options
* @param {string} [whichAxes] `xy`, 'x', or 'y'
* @param {boolean} [useTransition] Whether to use `zoom` transition
*/
export function doZoom(chart, percentZoomX, percentZoomY, focalPoint, zoomOptions, whichAxes, useTransition) {
const ca = chart.chartArea;
if (!focalPoint) {
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;

// 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)) {
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');

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

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

var scaleOptions = scale.options;
if (originalOptions[scale.id]) {
scaleOptions.min = originalOptions[scale.id].min;
scaleOptions.max = originalOptions[scale.id].max;
} else {
delete scaleOptions.min;
delete scaleOptions.max;
}
});
chart.update();
}

function panScale(scale, delta, panOptions) {
call(panFunctions[scale.type], [scale, delta, panOptions]);
}

export function doPan(chart, deltaX, deltaY, panOptions, panningScales) {
storeOriginalOptions(chart);
if (panOptions.enabled) {
var 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);
}
});

chart.update('none');

call(panOptions.onPan, [chart]);
}
}

153 changes: 153 additions & 0 deletions src/hammer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import Hammer from 'hammerjs';
import {doPan, doZoom} from './core';
import {getEnabledScalesByPoint} from './utils';

function createEnabler(chart, panOptions) {
return function(recognizer, event) {
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']) {
if (typeof panOptions.onPanRejected === 'function') {
panOptions.onPanRejected({
chart: chart,
event: event
});
}
return false;
}
return true;
};
}

export function startHammer(chart, options) {
const node = chart.canvas;
const {pan: panOptions, zoom: zoomOptions} = options;

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)
}));
}

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

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

// diagonal fingers will change both (xy) axes
var p = x / y;
var xy;
if (p > 0.3 && p < 1.7) {
xy = 'xy';
} else if (x > y) {
xy = 'x'; // x axis
} else {
xy = 'y'; // y axis
}

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

if (typeof zoomOptions.onZoom === 'function') {
zoomOptions.onZoom({chart: chart});
}

// 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});
}
});

let currentDeltaX = null;
let currentDeltaY = null;
let panning = false;
let panningScales = 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);
}
};

mc.on('panstart', function(e) {
if (panOptions.enabled) {
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);
}

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});
}
});

chart.$zoom._ghostClickHandler = function(e) {
if (panning && e.cancelable) {
e.stopImmediatePropagation();
e.preventDefault();
}
};
node.addEventListener('click', chart.$zoom._ghostClickHandler);

chart._mc = mc;
}

export function stopHammer(chart) {
const mc = chart._mc;
if (mc) {
mc.remove('pinchstart');
mc.remove('pinch');
mc.remove('pinchend');
mc.remove('panstart');
mc.remove('pan');
mc.remove('panend');
mc.destroy();
}
}
Loading