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 & export pan and zoom functions #464

Merged
merged 5 commits into from
Apr 25, 2021
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
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module.exports = {
'bar',
'log',
'time',
'api',
],
}
}
Expand Down
109 changes: 109 additions & 0 deletions docs/samples/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# API

```js chart-editor
// <block:data:1>
const NUMBER_CFG = {count: 20, min: -100, max: 100};
const data = {
datasets: [{
label: 'My First dataset',
borderColor: Utils.randomColor(0.4),
backgroundColor: Utils.randomColor(0.1),
pointBorderColor: Utils.randomColor(0.7),
pointBackgroundColor: Utils.randomColor(0.5),
pointBorderWidth: 1,
data: Utils.points(NUMBER_CFG),
}, {
label: 'My Second dataset',
borderColor: Utils.randomColor(0.4),
backgroundColor: Utils.randomColor(0.1),
pointBorderColor: Utils.randomColor(0.7),
pointBackgroundColor: Utils.randomColor(0.5),
pointBorderWidth: 1,
data: Utils.points(NUMBER_CFG),
}]
};
// </block:data>

// <block:scales:2>
const scaleOpts = {
reverse: true,
ticks: {
callback: (val, index, ticks) => index === 0 || index === ticks.length - 1 ? null : val,
},
grid: {
borderColor: Utils.randomColor(1),
color: 'rgba( 0, 0, 0, 0.1)',
},
title: {
display: true,
text: (ctx) => ctx.scale.axis + ' axis',
}
};
const scales = {
x: {
position: 'top',
},
y: {
position: 'right',
},
};
Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
// </block:scales>

// <block:config:1>
const config = {
type: 'scatter',
data: data,
options: {
scales: scales,
}
};
// </block:config>

// <block:actions:0>
// Note: changes to these actions are not applied to the buttons.
const actions = [
{
name: 'Zoom +10%',
handler(chart) {
chart.zoom(1.1);
}
}, {
name: 'Zoom -10%',
handler(chart) {
chart.zoom(0.9);
},
}, {
name: 'Zoom x +10%',
handler(chart) {
chart.zoom({x: 1.1});
}
}, {
name: 'Zoom x -10%',
handler(chart) {
chart.zoom({x: 0.9});
},
}, {
name: 'Pan x 100px',
handler(chart) {
chart.pan({x: 100});
}
}, {
name: 'Pan x -100px',
handler(chart) {
chart.pan({x: -100});
},
}, {
name: 'Reset zoom',
handler(chart) {
chart.resetZoom();
}
}
];
// </block:actions>

module.exports = {
actions,
config
};
```
5 changes: 2 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@rollup/plugin-json": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"chart.js": "^3.1.0",
"chart.js": "^3.2.0",
"chartjs-adapter-date-fns": "^2.0.0",
"chartjs-test-utils": "^0.2.2",
"concurrently": "^6.0.2",
Expand Down Expand Up @@ -71,7 +71,7 @@
"vuepress-theme-chartjs": "^0.2.0"
},
"peerDependencies": {
"chart.js": "^3.0.0"
"chart.js": "^3.2.0"
},
"dependencies": {
"hammerjs": "^2.0.8"
Expand Down
84 changes: 39 additions & 45 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,50 +23,41 @@ function zoomScale(scale, zoom, center, zoomOptions) {
call(fn, [scale, zoom, center, zoomOptions]);
}

function getCenter(chart) {
const ca = chart.chartArea;
return {
x: (ca.left + ca.right) / 2,
y: (ca.top + ca.bottom) / 2,
};
}

/**
* @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 {number | {x?: number, y?: number, focalPoint?: {x: number, y: number}}} zoom The zoom percentage or percentages and focal point
* @param {object} [options] The zoom options
* @param {boolean} [useTransition] Whether to use `zoom` transition
*/
export function doZoom(chart, percentZoomX, percentZoomY, focalPoint, zoomOptions, whichAxes, useTransition) {
if (!focalPoint) {
const ca = chart.chartArea;
focalPoint = {
x: (ca.left + ca.right) / 2,
y: (ca.top + ca.bottom) / 2,
};
}
export function doZoom(chart, zoom, options = {}, useTransition) {
const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof zoom === 'number' ? {x: zoom, y: zoom} : zoom;
const {mode = 'xy', overScaleMode} = options;

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

const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint.x, focalPoint.y, chart);

const xEnabled = x !== 1 && directionEnabled(mode, 'x', chart);
const yEnabled = y !== 1 && directionEnabled(mode, 'y', chart);
const enabledScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, focalPoint, 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);
if (scale.isHorizontal() && xEnabled) {
zoomScale(scale, x, focalPoint, options);
} else if (!scale.isHorizontal() && yEnabled) {
zoomScale(scale, y, focalPoint, options);
}
});

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

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

export function resetZoom(chart) {
Expand All @@ -90,22 +81,25 @@ function panScale(scale, delta, panOptions) {
call(fn, [scale, delta, panOptions]);
}

export function doPan(chart, deltaX, deltaY, panOptions, panningScales) {
export function doPan(chart, pan, options = {}, enabledScales) {
const {x = 0, y = 0} = typeof pan === 'number' ? {x: pan, y: pan} : pan;
const {mode = 'xy', onPan} = options;

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) {
panScale(scale, deltaX, panOptions);
} else if (!scale.isHorizontal() && directionEnabled(panMode, 'y', chart) && deltaY !== 0) {
panScale(scale, deltaY, panOptions);
}
});
const xEnabled = x !== 0 && directionEnabled(mode, 'x', chart);
const yEnabled = y !== 0 && directionEnabled(mode, 'y', chart);

each(enabledScales || chart.scales, function(scale) {
if (scale.isHorizontal() && xEnabled) {
panScale(scale, x, options);
} else if (!scale.isHorizontal() && yEnabled) {
panScale(scale, y, options);
}
});

chart.update('none');
chart.update('none');

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

51 changes: 33 additions & 18 deletions src/hammer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ 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';
import {directionEnabled, getEnabledScalesByPoint} from './utils';

function createEnabler(chart) {
const state = getState(chart);
Expand All @@ -26,28 +26,41 @@ function createEnabler(chart) {

function pinchAxes(p0, p1) {
// fingers position difference
const x = Math.abs(p0.clientX - p1.clientX);
const y = Math.abs(p0.clientY - p1.clientY);
const pinchX = Math.abs(p0.clientX - p1.clientX);
const pinchY = Math.abs(p0.clientY - p1.clientY);

// diagonal fingers will change both (xy) axes
const p = x / y;
return p > 0.3 && p < 1.7 ? 'xy' : x > y ? 'x' : 'y';
const p = pinchX / pinchY;
let x, y;
if (p > 0.3 && p < 1.7) {
x = y = true;
} else if (pinchX > pinchY) {
x = true;
} else {
y = true;
}
return {x, y};
}

function handlePinch(chart, state, e) {
if (state.scale) {
const {center, pointers} = e;
// Hammer reports the total scaling. We need the incremental amount
const zoom = 1 / state.scale * e.scale;
const zoomPercent = 1 / state.scale * e.scale;
const rect = e.target.getBoundingClientRect();
const focalPoint = {
x: center.x - rect.left,
y: center.y - rect.top
const pinch = pinchAxes(pointers[0], pointers[1]);
const options = state.options.zoom;
const mode = options.mode;
const zoom = {
x: pinch.x && directionEnabled(mode, 'x', chart) ? zoomPercent : 1,
y: pinch.y && directionEnabled(mode, 'y', chart) ? zoomPercent : 1,
focalPoint: {
x: center.x - rect.left,
y: center.y - rect.top
}
};

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

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

// Keep track of overall scale
state.scale = e.scale;
Expand All @@ -73,21 +86,23 @@ 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);
doPan(chart, {x: e.deltaX - delta.x, y: 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) {
const {enabled, overScaleMode} = state.options.pan;
if (!enabled) {
return;
}
const rect = e.target.getBoundingClientRect();
const x = e.center.x - rect.left;
const y = e.center.y - rect.top;
const point = {
x: e.center.x - rect.left,
y: e.center.y - rect.top
};

state.panScales = getEnabledScalesByPoint(panOptions, x, y, chart);
state.panScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, point, chart);
state.delta = {x: 0, y: 0};
handlePan(chart, state, e);
}
Expand Down
Loading