Skip to content

Commit

Permalink
Drag-to-zoom: filter clicks and pan.modifierKey (#484)
Browse files Browse the repository at this point in the history
* Drag-to-zoom: filter clicks and pan.modifierKey

* CC
  • Loading branch information
kurkle committed May 1, 2021
1 parent 756c898 commit e6d15ca
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 7 deletions.
9 changes: 7 additions & 2 deletions docs/samples/drag.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Drag To Zoom

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

```js chart-editor
// <block:data:1>
Expand Down Expand Up @@ -56,7 +56,8 @@ Object.keys(scales).forEach(scale => Object.assign(scales[scale], scaleOpts));
const dragColor = Utils.randomColor(0.4);
const zoomOptions = {
pan: {
enabled: false,
enabled: true,
modifierKey: 'ctrl',
},
zoom: {
enabled: true,
Expand Down Expand Up @@ -86,6 +87,9 @@ const config = {
text: (ctx) => 'Zoom: ' + zoomStatus()
}
},
onClick(e) {
console.log(e.type);
}
}
};
// </block:config>
Expand All @@ -108,5 +112,6 @@ const actions = [
module.exports = {
actions,
config,
output: 'Clicks are logged here'
};
```
10 changes: 8 additions & 2 deletions src/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,19 @@ function addHandler(chart, target, type, handler) {
export function mouseMove(chart, event) {
const state = getState(chart);
if (state.dragStart) {
state.dragging = true;
state.dragEnd = event;
chart.update('none');
}
}

export function mouseDown(chart, event) {
const state = getState(chart);
const {pan: panOptions, zoom: zoomOptions} = state.options;
const panKey = panOptions && panOptions.modifierKey;
if (panKey && event[panKey + 'Key']) {
return call(zoomOptions.onZoomRejected, [{chart, event}]);
}
state.dragStart = event;

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

// Remove drag start and end before chart update to stop drawing selected area
state.dragStart = null;
state.dragEnd = null;
state.dragStart = state.dragEnd = null;

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

setTimeout(() => (state.dragging = false), 500);
call(zoomOptions.onZoomComplete, [chart]);
}

Expand Down
4 changes: 2 additions & 2 deletions src/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export default {

beforeEvent(chart, args) {
const state = getState(chart);
if (args.event.type === 'click' && state.panning) {
// cancel the click event at pan end
if (args.event.type === 'click' && (state.panning || state.dragging)) {
// cancel the click event at pan/zoom end
return false;
}
},
Expand Down
14 changes: 14 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ jasmine.triggerWheelEvent = function(chart, init = {}) {
node.dispatchEvent(event);
};

jasmine.dispatchEvent = function(chart, type, pt, init = {}) {
const node = chart.canvas;
const rect = node.getBoundingClientRect();
const event = new MouseEvent(type, Object.assign({}, init, {
clientX: rect.left + pt.x,
clientY: rect.top + pt.y,
cancelable: true,
bubbles: true,
view: window
}));

node.dispatchEvent(event);
};

beforeEach(function() {
addMatchers();
});
Expand Down
75 changes: 74 additions & 1 deletion test/specs/zoom.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ describe('zoom', function() {
wheelEv[key + 'Key'] = true;
}

await jasmine.triggerWheelEvent(chart, wheelEv);
jasmine.triggerWheelEvent(chart, wheelEv);

if (pressed) {
expect(scaleX.options.min).not.toEqual(oldMinX);
Expand All @@ -315,6 +315,79 @@ describe('zoom', function() {
}
});

describe('drag with pan.modifierKey', function() {
for (const key of ['ctrl', 'alt', 'shift', 'meta']) {
for (const pressed of [true, false]) {
let chart, scaleX, scaleY;
it(`should ${pressed ? 'not ' : ''}change ${pressed ? 'without' : 'with'} key ${key}`, async function() {
const rejectedSpy = jasmine.createSpy('zoomRejected');
const clickSpy = jasmine.createSpy('clicked');
chart = window.acquireChart({
type: 'line',
data,
options: {
scales: {
x: {
type: 'linear',
min: 0,
max: 10
},
y: {
type: 'linear'
}
},
plugins: {
zoom: {
pan: {
modifierKey: key,
},
zoom: {
enabled: true,
drag: true,
mode: 'x',
onZoomRejected: rejectedSpy
}
}
},
onClick: clickSpy
}
});

scaleX = chart.scales.x;
scaleY = chart.scales.y;

const oldMinX = scaleX.options.min;
const oldMaxX = scaleX.options.max;

const pt = {
x: scaleX.getPixelForValue(1.5),
y: scaleY.getPixelForValue(1.1),
};
const pt2 = {x: pt.x + 20, y: pt.y + 20};
const init = {};
if (pressed) {
init[key + 'Key'] = true;
}

jasmine.dispatchEvent(chart, 'mousedown', pt, init);
jasmine.dispatchEvent(chart, 'mousemove', pt2, init);
jasmine.dispatchEvent(chart, 'mouseup', pt2, init);

if (pressed) {
expect(scaleX.options.min).toEqual(oldMinX);
expect(scaleX.options.max).toEqual(oldMaxX);
expect(rejectedSpy).toHaveBeenCalled();
} else {
expect(scaleX.options.min).not.toEqual(oldMinX);
expect(scaleX.options.max).not.toEqual(oldMaxX);
expect(rejectedSpy).not.toHaveBeenCalled();
}
expect(clickSpy).not.toHaveBeenCalled();
});
}
}
});

describe('with overScaleMode = y and mode = xy', function() {
const config = {
type: 'line',
Expand Down

0 comments on commit e6d15ca

Please sign in to comment.