Skip to content

Commit e6d15ca

Browse files
authored
Drag-to-zoom: filter clicks and pan.modifierKey (#484)
* Drag-to-zoom: filter clicks and pan.modifierKey * CC
1 parent 756c898 commit e6d15ca

File tree

5 files changed

+105
-7
lines changed

5 files changed

+105
-7
lines changed

docs/samples/drag.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Drag To Zoom
22

3-
Zooming is performed by clicking and selecting an area over the chart with the mouse.
3+
Zooming is performed by clicking and selecting an area over the chart with the mouse. Pan is activated by keeping `ctrl` pressed.
44

55
```js chart-editor
66
// <block:data:1>
@@ -56,7 +56,8 @@ Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
5656
const dragColor = Utils.randomColor(0.4);
5757
const zoomOptions = {
5858
pan: {
59-
enabled: false,
59+
enabled: true,
60+
modifierKey: 'ctrl',
6061
},
6162
zoom: {
6263
enabled: true,
@@ -86,6 +87,9 @@ const config = {
8687
text: (ctx) => 'Zoom: ' + zoomStatus()
8788
}
8889
},
90+
onClick(e) {
91+
console.log(e.type);
92+
}
8993
}
9094
};
9195
// </block:config>
@@ -108,5 +112,6 @@ const actions = [
108112
module.exports = {
109113
actions,
110114
config,
115+
output: 'Clicks are logged here'
111116
};
112117
```

src/handlers.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@ function addHandler(chart, target, type, handler) {
2222
export function mouseMove(chart, event) {
2323
const state = getState(chart);
2424
if (state.dragStart) {
25+
state.dragging = true;
2526
state.dragEnd = event;
2627
chart.update('none');
2728
}
2829
}
2930

3031
export function mouseDown(chart, event) {
3132
const state = getState(chart);
33+
const {pan: panOptions, zoom: zoomOptions} = state.options;
34+
const panKey = panOptions && panOptions.modifierKey;
35+
if (panKey && event[panKey + 'Key']) {
36+
return call(zoomOptions.onZoomRejected, [{chart, event}]);
37+
}
3238
state.dragStart = event;
3339

3440
addHandler(chart, chart.canvas, 'mousemove', mouseMove);
@@ -76,8 +82,7 @@ export function mouseUp(chart, event) {
7682
const {width: dragDistanceX, height: dragDistanceY} = rect;
7783

7884
// Remove drag start and end before chart update to stop drawing selected area
79-
state.dragStart = null;
80-
state.dragEnd = null;
85+
state.dragStart = state.dragEnd = null;
8186

8287
const zoomThreshold = zoomOptions.threshold || 0;
8388
if (dragDistanceX <= zoomThreshold && dragDistanceY <= zoomThreshold) {
@@ -95,6 +100,7 @@ export function mouseUp(chart, event) {
95100
};
96101
zoom(chart, amount, 'zoom');
97102

103+
setTimeout(() => (state.dragging = false), 500);
98104
call(zoomOptions.onZoomComplete, [chart]);
99105
}
100106

src/plugin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ export default {
4242

4343
beforeEvent(chart, args) {
4444
const state = getState(chart);
45-
if (args.event.type === 'click' && state.panning) {
46-
// cancel the click event at pan end
45+
if (args.event.type === 'click' && (state.panning || state.dragging)) {
46+
// cancel the click event at pan/zoom end
4747
return false;
4848
}
4949
},

test/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ jasmine.triggerWheelEvent = function(chart, init = {}) {
2626
node.dispatchEvent(event);
2727
};
2828

29+
jasmine.dispatchEvent = function(chart, type, pt, init = {}) {
30+
const node = chart.canvas;
31+
const rect = node.getBoundingClientRect();
32+
const event = new MouseEvent(type, Object.assign({}, init, {
33+
clientX: rect.left + pt.x,
34+
clientY: rect.top + pt.y,
35+
cancelable: true,
36+
bubbles: true,
37+
view: window
38+
}));
39+
40+
node.dispatchEvent(event);
41+
};
42+
2943
beforeEach(function() {
3044
addMatchers();
3145
});

test/specs/zoom.spec.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ describe('zoom', function() {
299299
wheelEv[key + 'Key'] = true;
300300
}
301301

302-
await jasmine.triggerWheelEvent(chart, wheelEv);
302+
jasmine.triggerWheelEvent(chart, wheelEv);
303303

304304
if (pressed) {
305305
expect(scaleX.options.min).not.toEqual(oldMinX);
@@ -315,6 +315,79 @@ describe('zoom', function() {
315315
}
316316
});
317317

318+
describe('drag with pan.modifierKey', function() {
319+
for (const key of ['ctrl', 'alt', 'shift', 'meta']) {
320+
for (const pressed of [true, false]) {
321+
let chart, scaleX, scaleY;
322+
it(`should ${pressed ? 'not ' : ''}change ${pressed ? 'without' : 'with'} key ${key}`, async function() {
323+
const rejectedSpy = jasmine.createSpy('zoomRejected');
324+
const clickSpy = jasmine.createSpy('clicked');
325+
chart = window.acquireChart({
326+
type: 'line',
327+
data,
328+
options: {
329+
scales: {
330+
x: {
331+
type: 'linear',
332+
min: 0,
333+
max: 10
334+
},
335+
y: {
336+
type: 'linear'
337+
}
338+
},
339+
plugins: {
340+
zoom: {
341+
pan: {
342+
modifierKey: key,
343+
},
344+
zoom: {
345+
enabled: true,
346+
drag: true,
347+
mode: 'x',
348+
onZoomRejected: rejectedSpy
349+
}
350+
}
351+
},
352+
onClick: clickSpy
353+
}
354+
});
355+
356+
scaleX = chart.scales.x;
357+
scaleY = chart.scales.y;
358+
359+
const oldMinX = scaleX.options.min;
360+
const oldMaxX = scaleX.options.max;
361+
362+
const pt = {
363+
x: scaleX.getPixelForValue(1.5),
364+
y: scaleY.getPixelForValue(1.1),
365+
};
366+
const pt2 = {x: pt.x + 20, y: pt.y + 20};
367+
const init = {};
368+
if (pressed) {
369+
init[key + 'Key'] = true;
370+
}
371+
372+
jasmine.dispatchEvent(chart, 'mousedown', pt, init);
373+
jasmine.dispatchEvent(chart, 'mousemove', pt2, init);
374+
jasmine.dispatchEvent(chart, 'mouseup', pt2, init);
375+
376+
if (pressed) {
377+
expect(scaleX.options.min).toEqual(oldMinX);
378+
expect(scaleX.options.max).toEqual(oldMaxX);
379+
expect(rejectedSpy).toHaveBeenCalled();
380+
} else {
381+
expect(scaleX.options.min).not.toEqual(oldMinX);
382+
expect(scaleX.options.max).not.toEqual(oldMaxX);
383+
expect(rejectedSpy).not.toHaveBeenCalled();
384+
}
385+
expect(clickSpy).not.toHaveBeenCalled();
386+
});
387+
}
388+
}
389+
});
390+
318391
describe('with overScaleMode = y and mode = xy', function() {
319392
const config = {
320393
type: 'line',

0 commit comments

Comments
 (0)