Skip to content

Commit 15b227c

Browse files
authored
Refactor & export pan and zoom functions (#464)
* Refactor & export pan and zoom functions * Fix + optimise * Fix tests * typo fix * lint err
1 parent 3b106e1 commit 15b227c

File tree

13 files changed

+261
-105
lines changed

13 files changed

+261
-105
lines changed

docs/.vuepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ module.exports = {
6969
'bar',
7070
'log',
7171
'time',
72+
'api',
7273
],
7374
}
7475
}

docs/samples/api.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# API
2+
3+
```js chart-editor
4+
// <block:data:1>
5+
const NUMBER_CFG = {count: 20, min: -100, max: 100};
6+
const data = {
7+
datasets: [{
8+
label: 'My First dataset',
9+
borderColor: Utils.randomColor(0.4),
10+
backgroundColor: Utils.randomColor(0.1),
11+
pointBorderColor: Utils.randomColor(0.7),
12+
pointBackgroundColor: Utils.randomColor(0.5),
13+
pointBorderWidth: 1,
14+
data: Utils.points(NUMBER_CFG),
15+
}, {
16+
label: 'My Second dataset',
17+
borderColor: Utils.randomColor(0.4),
18+
backgroundColor: Utils.randomColor(0.1),
19+
pointBorderColor: Utils.randomColor(0.7),
20+
pointBackgroundColor: Utils.randomColor(0.5),
21+
pointBorderWidth: 1,
22+
data: Utils.points(NUMBER_CFG),
23+
}]
24+
};
25+
// </block:data>
26+
27+
// <block:scales:2>
28+
const scaleOpts = {
29+
reverse: true,
30+
ticks: {
31+
callback: (val, index, ticks) => index === 0 || index === ticks.length - 1 ? null : val,
32+
},
33+
grid: {
34+
borderColor: Utils.randomColor(1),
35+
color: 'rgba( 0, 0, 0, 0.1)',
36+
},
37+
title: {
38+
display: true,
39+
text: (ctx) => ctx.scale.axis + ' axis',
40+
}
41+
};
42+
const scales = {
43+
x: {
44+
position: 'top',
45+
},
46+
y: {
47+
position: 'right',
48+
},
49+
};
50+
Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
51+
// </block:scales>
52+
53+
// <block:config:1>
54+
const config = {
55+
type: 'scatter',
56+
data: data,
57+
options: {
58+
scales: scales,
59+
}
60+
};
61+
// </block:config>
62+
63+
// <block:actions:0>
64+
// Note: changes to these actions are not applied to the buttons.
65+
const actions = [
66+
{
67+
name: 'Zoom +10%',
68+
handler(chart) {
69+
chart.zoom(1.1);
70+
}
71+
}, {
72+
name: 'Zoom -10%',
73+
handler(chart) {
74+
chart.zoom(0.9);
75+
},
76+
}, {
77+
name: 'Zoom x +10%',
78+
handler(chart) {
79+
chart.zoom({x: 1.1});
80+
}
81+
}, {
82+
name: 'Zoom x -10%',
83+
handler(chart) {
84+
chart.zoom({x: 0.9});
85+
},
86+
}, {
87+
name: 'Pan x 100px',
88+
handler(chart) {
89+
chart.pan({x: 100});
90+
}
91+
}, {
92+
name: 'Pan x -100px',
93+
handler(chart) {
94+
chart.pan({x: -100});
95+
},
96+
}, {
97+
name: 'Reset zoom',
98+
handler(chart) {
99+
chart.resetZoom();
100+
}
101+
}
102+
];
103+
// </block:actions>
104+
105+
module.exports = {
106+
actions,
107+
config
108+
};
109+
```

package-lock.json

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@rollup/plugin-json": "^4.1.0",
3636
"@typescript-eslint/eslint-plugin": "^4.22.0",
3737
"@typescript-eslint/parser": "^4.22.0",
38-
"chart.js": "^3.1.0",
38+
"chart.js": "^3.2.0",
3939
"chartjs-adapter-date-fns": "^2.0.0",
4040
"chartjs-test-utils": "^0.2.2",
4141
"concurrently": "^6.0.2",
@@ -71,7 +71,7 @@
7171
"vuepress-theme-chartjs": "^0.2.0"
7272
},
7373
"peerDependencies": {
74-
"chart.js": "^3.0.0"
74+
"chart.js": "^3.2.0"
7575
},
7676
"dependencies": {
7777
"hammerjs": "^2.0.8"

src/core.js

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -23,50 +23,41 @@ function zoomScale(scale, zoom, center, zoomOptions) {
2323
call(fn, [scale, zoom, center, zoomOptions]);
2424
}
2525

26+
function getCenter(chart) {
27+
const ca = chart.chartArea;
28+
return {
29+
x: (ca.left + ca.right) / 2,
30+
y: (ca.top + ca.bottom) / 2,
31+
};
32+
}
33+
2634
/**
2735
* @param chart The chart instance
28-
* @param {number} percentZoomX The zoom percentage in the x direction
29-
* @param {number} percentZoomY The zoom percentage in the y direction
30-
* @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"
31-
* @param {object} zoomOptions The zoom options
32-
* @param {string} [whichAxes] `xy`, 'x', or 'y'
36+
* @param {number | {x?: number, y?: number, focalPoint?: {x: number, y: number}}} zoom The zoom percentage or percentages and focal point
37+
* @param {object} [options] The zoom options
3338
* @param {boolean} [useTransition] Whether to use `zoom` transition
3439
*/
35-
export function doZoom(chart, percentZoomX, percentZoomY, focalPoint, zoomOptions, whichAxes, useTransition) {
36-
if (!focalPoint) {
37-
const ca = chart.chartArea;
38-
focalPoint = {
39-
x: (ca.left + ca.right) / 2,
40-
y: (ca.top + ca.bottom) / 2,
41-
};
42-
}
40+
export function doZoom(chart, zoom, options = {}, useTransition) {
41+
const {x = 1, y = 1, focalPoint = getCenter(chart)} = typeof zoom === 'number' ? {x: zoom, y: zoom} : zoom;
42+
const {mode = 'xy', overScaleMode} = options;
4343

4444
storeOriginalScaleLimits(chart);
45-
// Do the zoom here
46-
const zoomMode = typeof zoomOptions.mode === 'function' ? zoomOptions.mode({chart: chart}) : zoomOptions.mode;
47-
48-
// Which axes should be modified when fingers were used.
49-
let _whichAxes;
50-
if (zoomMode === 'xy' && whichAxes !== undefined) {
51-
// based on fingers positions
52-
_whichAxes = whichAxes;
53-
} else {
54-
// no effect
55-
_whichAxes = 'xy';
56-
}
57-
58-
const enabledScales = getEnabledScalesByPoint(zoomOptions, focalPoint.x, focalPoint.y, chart);
45+
46+
const xEnabled = x !== 1 && directionEnabled(mode, 'x', chart);
47+
const yEnabled = y !== 1 && directionEnabled(mode, 'y', chart);
48+
const enabledScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, focalPoint, chart);
49+
5950
each(enabledScales || chart.scales, function(scale) {
60-
if (scale.isHorizontal() && directionEnabled(zoomMode, 'x', chart) && directionEnabled(_whichAxes, 'x', chart)) {
61-
zoomScale(scale, percentZoomX, focalPoint, zoomOptions);
62-
} else if (!scale.isHorizontal() && directionEnabled(zoomMode, 'y', chart) && directionEnabled(_whichAxes, 'y', chart)) {
63-
zoomScale(scale, percentZoomY, focalPoint, zoomOptions);
51+
if (scale.isHorizontal() && xEnabled) {
52+
zoomScale(scale, x, focalPoint, options);
53+
} else if (!scale.isHorizontal() && yEnabled) {
54+
zoomScale(scale, y, focalPoint, options);
6455
}
6556
});
6657

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

69-
call(zoomOptions.onZoom, [chart]);
60+
call(options.onZoom, [chart]);
7061
}
7162

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

93-
export function doPan(chart, deltaX, deltaY, panOptions, panningScales) {
84+
export function doPan(chart, pan, options = {}, enabledScales) {
85+
const {x = 0, y = 0} = typeof pan === 'number' ? {x: pan, y: pan} : pan;
86+
const {mode = 'xy', onPan} = options;
87+
9488
storeOriginalScaleLimits(chart);
95-
if (panOptions.enabled) {
96-
const panMode = typeof panOptions.mode === 'function' ? panOptions.mode({chart}) : panOptions.mode;
9789

98-
each(panningScales || chart.scales, function(scale) {
99-
if (scale.isHorizontal() && directionEnabled(panMode, 'x', chart) && deltaX !== 0) {
100-
panScale(scale, deltaX, panOptions);
101-
} else if (!scale.isHorizontal() && directionEnabled(panMode, 'y', chart) && deltaY !== 0) {
102-
panScale(scale, deltaY, panOptions);
103-
}
104-
});
90+
const xEnabled = x !== 0 && directionEnabled(mode, 'x', chart);
91+
const yEnabled = y !== 0 && directionEnabled(mode, 'y', chart);
92+
93+
each(enabledScales || chart.scales, function(scale) {
94+
if (scale.isHorizontal() && xEnabled) {
95+
panScale(scale, x, options);
96+
} else if (!scale.isHorizontal() && yEnabled) {
97+
panScale(scale, y, options);
98+
}
99+
});
105100

106-
chart.update('none');
101+
chart.update('none');
107102

108-
call(panOptions.onPan, [chart]);
109-
}
103+
call(onPan, [chart]);
110104
}
111105

src/hammer.js

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {callback as call} from 'chart.js/helpers';
22
import Hammer from 'hammerjs';
33
import {doPan, doZoom} from './core';
44
import {getState} from './state';
5-
import {getEnabledScalesByPoint} from './utils';
5+
import {directionEnabled, getEnabledScalesByPoint} from './utils';
66

77
function createEnabler(chart) {
88
const state = getState(chart);
@@ -26,28 +26,41 @@ function createEnabler(chart) {
2626

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

3232
// diagonal fingers will change both (xy) axes
33-
const p = x / y;
34-
return p > 0.3 && p < 1.7 ? 'xy' : x > y ? 'x' : 'y';
33+
const p = pinchX / pinchY;
34+
let x, y;
35+
if (p > 0.3 && p < 1.7) {
36+
x = y = true;
37+
} else if (pinchX > pinchY) {
38+
x = true;
39+
} else {
40+
y = true;
41+
}
42+
return {x, y};
3543
}
3644

3745
function handlePinch(chart, state, e) {
3846
if (state.scale) {
3947
const {center, pointers} = e;
4048
// Hammer reports the total scaling. We need the incremental amount
41-
const zoom = 1 / state.scale * e.scale;
49+
const zoomPercent = 1 / state.scale * e.scale;
4250
const rect = e.target.getBoundingClientRect();
43-
const focalPoint = {
44-
x: center.x - rect.left,
45-
y: center.y - rect.top
51+
const pinch = pinchAxes(pointers[0], pointers[1]);
52+
const options = state.options.zoom;
53+
const mode = options.mode;
54+
const zoom = {
55+
x: pinch.x && directionEnabled(mode, 'x', chart) ? zoomPercent : 1,
56+
y: pinch.y && directionEnabled(mode, 'y', chart) ? zoomPercent : 1,
57+
focalPoint: {
58+
x: center.x - rect.left,
59+
y: center.y - rect.top
60+
}
4661
};
4762

48-
const xy = pinchAxes(pointers[0], pointers[1]);
49-
50-
doZoom(chart, zoom, zoom, focalPoint, state.options.zoom, xy);
63+
doZoom(chart, zoom, options);
5164

5265
// Keep track of overall scale
5366
state.scale = e.scale;
@@ -73,21 +86,23 @@ function handlePan(chart, state, e) {
7386
const delta = state.delta;
7487
if (delta !== null) {
7588
state.panning = true;
76-
doPan(chart, e.deltaX - delta.x, e.deltaY - delta.y, state.options.pan, state.panScales);
89+
doPan(chart, {x: e.deltaX - delta.x, y: e.deltaY - delta.y}, state.options.pan, state.panScales);
7790
state.delta = {x: e.deltaX, y: e.deltaY};
7891
}
7992
}
8093

8194
function startPan(chart, state, e) {
82-
const panOptions = state.options.pan;
83-
if (!panOptions.enabled) {
95+
const {enabled, overScaleMode} = state.options.pan;
96+
if (!enabled) {
8497
return;
8598
}
8699
const rect = e.target.getBoundingClientRect();
87-
const x = e.center.x - rect.left;
88-
const y = e.center.y - rect.top;
100+
const point = {
101+
x: e.center.x - rect.left,
102+
y: e.center.y - rect.top
103+
};
89104

90-
state.panScales = getEnabledScalesByPoint(panOptions, x, y, chart);
105+
state.panScales = overScaleMode && getEnabledScalesByPoint(overScaleMode, point, chart);
91106
state.delta = {x: 0, y: 0};
92107
handlePan(chart, state, e);
93108
}

0 commit comments

Comments
 (0)