Skip to content

Commit 3ba323e

Browse files
authored
Add modifierKey option for drag-to-zoom (#555)
* Add modifierKey option for drag-to-zoom * Add some missing things * Tiny optimization * Move things to utils
1 parent 09e0eaa commit 3ba323e

File tree

8 files changed

+97
-16
lines changed

8 files changed

+97
-16
lines changed

docs/guide/options.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const chart = new Chart('id', {
6565
| ---- | -----| ------- | -----------
6666
| `enabled` | `boolean` | `false` | Enable zooming via mouse wheel
6767
| `speed` | `number` | `0.1` | Factor of zoom speed via mouse wheel
68-
| `modifierKey` | `'ctrl'`\|`'alt'`\|`'shift'`\|`'meta'` | `null` | Modifier key required for zooming with mouse
68+
| `modifierKey` | `'ctrl'`\|`'alt'`\|`'shift'`\|`'meta'` | `null` | Modifier key required for zooming via mouse wheel
6969

7070
#### Drag options
7171

@@ -76,6 +76,7 @@ const chart = new Chart('id', {
7676
| `borderColor` | `Color` | `'rgba(225,225,225)'` | Stroke color
7777
| `borderWidth` | `number` | `0` | Stroke width
7878
| `threshold` | `number` | `0` | Minimal zoom distance required before actually applying zoom
79+
| `modifierKey` | `'ctrl'`\|`'alt'`\|`'shift'`\|`'meta'` | `null` | Modifier key required for drag-to-zoom
7980

8081
#### Pinch options
8182

src/hammer.js

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

77
function createEnabler(chart, state) {
88
return function(recognizer, event) {
9-
const panOptions = state.options.pan;
9+
const {pan: panOptions, zoom: zoomOptions = {}} = state.options;
1010
if (!panOptions || !panOptions.enabled) {
1111
return false;
1212
}
13-
if (!event || !event.srcEvent) { // Sometimes Hammer queries this with a null event.
13+
const srcEvent = event && event.srcEvent;
14+
if (!srcEvent) { // Sometimes Hammer queries this with a null event.
1415
return true;
1516
}
16-
const modifierKey = panOptions.modifierKey;
17-
const requireModifier = modifierKey && (event.pointerType === 'mouse');
18-
if (!state.panning && requireModifier && !event.srcEvent[modifierKey + 'Key']) {
17+
if (!state.panning && event.pointerType === 'mouse' && (
18+
keyNotPressed(getModifierKey(panOptions), srcEvent) || keyPressed(getModifierKey(zoomOptions), srcEvent))
19+
) {
1920
call(panOptions.onPanRejected, [{chart, event}]);
2021
return false;
2122
}

src/handlers.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {directionEnabled, debounce} from './utils';
1+
import {directionEnabled, debounce, keyNotPressed, getModifierKey, keyPressed} from './utils';
22
import {zoom, zoomRect} from './core';
33
import {callback as call} from 'chart.js/helpers';
44
import {getState} from './state';
@@ -46,9 +46,8 @@ function zoomStart(chart, event, zoomOptions) {
4646

4747
export function mouseDown(chart, event) {
4848
const state = getState(chart);
49-
const {pan: panOptions, zoom: zoomOptions} = state.options;
50-
const panKey = panOptions && panOptions.modifierKey;
51-
if (panKey && event[panKey + 'Key']) {
49+
const {pan: panOptions, zoom: zoomOptions = {}} = state.options;
50+
if (keyPressed(getModifierKey(panOptions), event) || keyNotPressed(getModifierKey(zoomOptions.drag), event)) {
5251
return call(zoomOptions.onZoomRejected, [{chart, event}]);
5352
}
5453

@@ -119,10 +118,9 @@ export function mouseUp(chart, event) {
119118
}
120119

121120
function wheelPreconditions(chart, event, zoomOptions) {
122-
const {wheel: wheelOptions, onZoomRejected} = zoomOptions;
123121
// Before preventDefault, check if the modifier key required and pressed
124-
if (wheelOptions.modifierKey && !event[wheelOptions.modifierKey + 'Key']) {
125-
call(onZoomRejected, [{chart, event}]);
122+
if (keyNotPressed(getModifierKey(zoomOptions.wheel), event)) {
123+
call(zoomOptions.onZoomRejected, [{chart, event}]);
126124
return;
127125
}
128126

src/plugin.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ export default {
2525
modifierKey: null
2626
},
2727
drag: {
28-
enabled: false
28+
enabled: false,
29+
modifierKey: null
2930
},
3031
pinch: {
3132
enabled: false

src/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import {each} from 'chart.js/helpers';
22

3+
export const getModifierKey = opts => opts && opts.enabled && opts.modifierKey;
4+
export const keyPressed = (key, event) => key && event[key + 'Key'];
5+
export const keyNotPressed = (key, event) => key && !event[key + 'Key'];
6+
37
/**
48
* @param {string|function} mode can be 'x', 'y' or 'xy'
59
* @param {string} dir can be 'x' or 'y'

test/specs/defaults.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ describe('defaults', function() {
1313
modifierKey: null
1414
},
1515
drag: {
16-
enabled: false
16+
enabled: false,
17+
modifierKey: null
1718
},
1819
pinch: {
1920
enabled: false

test/specs/zoom.spec.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ describe('zoom', function() {
346346
plugins: {
347347
zoom: {
348348
pan: {
349+
enabled: true,
349350
modifierKey: key,
350351
},
351352
zoom: {
@@ -396,6 +397,75 @@ describe('zoom', function() {
396397
}
397398
});
398399

400+
describe('drag with modifierKey', function() {
401+
for (const key of ['ctrl', 'alt', 'shift', 'meta']) {
402+
for (const pressed of [true, false]) {
403+
let chart, scaleX, scaleY;
404+
it(`should ${pressed ? '' : 'not '}change ${pressed ? 'with' : 'without'} key ${key}`, async function() {
405+
const rejectedSpy = jasmine.createSpy('wheelFailed');
406+
chart = window.acquireChart({
407+
type: 'line',
408+
data,
409+
options: {
410+
scales: {
411+
x: {
412+
type: 'linear',
413+
min: 0,
414+
max: 10
415+
},
416+
y: {
417+
type: 'linear'
418+
}
419+
},
420+
plugins: {
421+
zoom: {
422+
zoom: {
423+
drag: {
424+
enabled: true,
425+
modifierKey: key,
426+
},
427+
mode: 'x',
428+
onZoomRejected: rejectedSpy
429+
}
430+
}
431+
}
432+
}
433+
});
434+
435+
scaleX = chart.scales.x;
436+
scaleY = chart.scales.y;
437+
438+
const oldMinX = scaleX.options.min;
439+
const oldMaxX = scaleX.options.max;
440+
441+
const pt = {
442+
x: scaleX.getPixelForValue(1.5),
443+
y: scaleY.getPixelForValue(1.1),
444+
};
445+
const pt2 = {x: pt.x + 20, y: pt.y + 20};
446+
const init = {};
447+
if (pressed) {
448+
init[key + 'Key'] = true;
449+
}
450+
451+
jasmine.dispatchEvent(chart, 'mousedown', pt, init);
452+
jasmine.dispatchEvent(chart, 'mousemove', pt2, init);
453+
jasmine.dispatchEvent(chart, 'mouseup', pt2, init);
454+
455+
if (pressed) {
456+
expect(scaleX.options.min).not.toEqual(oldMinX);
457+
expect(scaleX.options.max).not.toEqual(oldMaxX);
458+
expect(rejectedSpy).not.toHaveBeenCalled();
459+
} else {
460+
expect(scaleX.options.min).toEqual(oldMinX);
461+
expect(scaleX.options.max).toEqual(oldMaxX);
462+
expect(rejectedSpy).toHaveBeenCalled();
463+
}
464+
});
465+
}
466+
}
467+
});
468+
399469
describe('with overScaleMode = y and mode = xy', function() {
400470
const config = {
401471
type: 'line',

types/options.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ export interface DragOptions {
4747
* Background color of the drag area
4848
*/
4949
backgroundColor?: Color;
50+
51+
/**
52+
* Modifier key required for drag-to-zoom
53+
*/
54+
modifierKey?: Key;
5055
}
5156

5257
export interface PinchOptions {

0 commit comments

Comments
 (0)