Skip to content

Commit 2dabfe4

Browse files
authored
Clamp drag rectangle to chart area (#896)
* Clamp drag rectangle to chart area I believe this is a better UI, and it also fixes some issues with incorrect zoom ranges when dragging the rectangle outside of the chart area. To help implement this, I copied the clamp helper function from chartjs-plugin-annotation. Fixes #895 * Combine beginPoint and endPoint to fix ESLint max arguments * Add a test
1 parent b489729 commit 2dabfe4

File tree

2 files changed

+83
-17
lines changed

2 files changed

+83
-17
lines changed

src/handlers.js

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ import {zoom, zoomRect} from './core';
33
import {callback as call, getRelativePosition, _isPointInArea} from 'chart.js/helpers';
44
import {getState} from './state';
55

6+
/**
7+
* @param {number} x
8+
* @param {number} from
9+
* @param {number} to
10+
* @returns {number}
11+
*/
12+
const clamp = (x, from, to) => Math.min(to, Math.max(from, x));
13+
614
function removeHandler(chart, type) {
715
const {handlers} = getState(chart);
816
const handler = handlers[type];
@@ -96,9 +104,9 @@ export function mouseDown(chart, event) {
96104
addHandler(chart, window.document, 'keydown', keyDown);
97105
}
98106

99-
function applyAspectRatio(endPoint, beginPoint, aspectRatio) {
100-
let width = endPoint.x - beginPoint.x;
101-
let height = endPoint.y - beginPoint.y;
107+
function applyAspectRatio({begin, end}, aspectRatio) {
108+
let width = end.x - begin.x;
109+
let height = end.y - begin.y;
102110
const ratio = Math.abs(width / height);
103111

104112
if (ratio > aspectRatio) {
@@ -107,41 +115,43 @@ function applyAspectRatio(endPoint, beginPoint, aspectRatio) {
107115
height = Math.sign(height) * Math.abs(width / aspectRatio);
108116
}
109117

110-
endPoint.x = beginPoint.x + width;
111-
endPoint.y = beginPoint.y + height;
118+
end.x = begin.x + width;
119+
end.y = begin.y + height;
112120
}
113121

114-
function applyMinMaxProps(rect, beginPoint, endPoint, {min, max, prop}) {
115-
rect[min] = Math.max(0, Math.min(beginPoint[prop], endPoint[prop]));
116-
rect[max] = Math.max(beginPoint[prop], endPoint[prop]);
122+
function applyMinMaxProps(rect, chartArea, points, {min, max, prop}) {
123+
rect[min] = clamp(Math.min(points.begin[prop], points.end[prop]), chartArea[min], chartArea[max]);
124+
rect[max] = clamp(Math.max(points.begin[prop], points.end[prop]), chartArea[min], chartArea[max]);
117125
}
118126

119-
function getReplativePoints(chart, points, maintainAspectRatio) {
120-
const beginPoint = getPointPosition(points.dragStart, chart);
121-
const endPoint = getPointPosition(points.dragEnd, chart);
127+
function getRelativePoints(chart, pointEvents, maintainAspectRatio) {
128+
const points = {
129+
begin: getPointPosition(pointEvents.dragStart, chart),
130+
end: getPointPosition(pointEvents.dragEnd, chart),
131+
};
122132

123133
if (maintainAspectRatio) {
124134
const aspectRatio = chart.chartArea.width / chart.chartArea.height;
125-
applyAspectRatio(endPoint, beginPoint, aspectRatio);
135+
applyAspectRatio(points, aspectRatio);
126136
}
127137

128-
return {beginPoint, endPoint};
138+
return points;
129139
}
130140

131-
export function computeDragRect(chart, mode, points, maintainAspectRatio) {
141+
export function computeDragRect(chart, mode, pointEvents, maintainAspectRatio) {
132142
const xEnabled = directionEnabled(mode, 'x', chart);
133143
const yEnabled = directionEnabled(mode, 'y', chart);
134144
const {top, left, right, bottom, width: chartWidth, height: chartHeight} = chart.chartArea;
135145
const rect = {top, left, right, bottom};
136146

137-
const {beginPoint, endPoint} = getReplativePoints(chart, points, maintainAspectRatio && xEnabled && yEnabled);
147+
const points = getRelativePoints(chart, pointEvents, maintainAspectRatio && xEnabled && yEnabled);
138148

139149
if (xEnabled) {
140-
applyMinMaxProps(rect, beginPoint, endPoint, {min: 'left', max: 'right', prop: 'x'});
150+
applyMinMaxProps(rect, chart.chartArea, points, {min: 'left', max: 'right', prop: 'x'});
141151
}
142152

143153
if (yEnabled) {
144-
applyMinMaxProps(rect, beginPoint, endPoint, {min: 'top', max: 'bottom', prop: 'y'});
154+
applyMinMaxProps(rect, chart.chartArea, points, {min: 'top', max: 'bottom', prop: 'y'});
145155
}
146156

147157
const width = rect.right - rect.left;

test/specs/zoom.drag.spec.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,62 @@ describe('zoom with drag', function() {
396396
}
397397
});
398398

399+
describe('coordinate handling', function() {
400+
let chart, scaleX, scaleY;
401+
402+
it('handles dragging to the right edge of the chart', function() {
403+
chart = window.acquireChart({
404+
type: 'line',
405+
data,
406+
options: {
407+
scales: {
408+
xScale0: {
409+
id: 'xScale0',
410+
type: 'linear',
411+
min: 0,
412+
max: 4
413+
},
414+
yScale0: {
415+
id: 'yScale0',
416+
type: 'linear',
417+
min: 0,
418+
max: 4,
419+
}
420+
},
421+
plugins: {
422+
zoom: {
423+
zoom: {
424+
drag: {
425+
enabled: true
426+
},
427+
mode: 'xy'
428+
}
429+
}
430+
}
431+
}
432+
}, {
433+
wrapper: {style: 'position: absolute; left: 50px; top: 50px;'}
434+
});
435+
436+
scaleX = chart.scales.xScale0;
437+
scaleY = chart.scales.yScale0;
438+
439+
jasmine.triggerMouseEvent(chart, 'mousedown', {
440+
x: scaleX.getPixelForValue(1.5),
441+
y: scaleY.getPixelForValue(1.2)
442+
});
443+
jasmine.triggerMouseEvent(chart, 'mouseup', {
444+
x: scaleX.getPixelForValue(4) + 5,
445+
y: scaleY.getPixelForValue(1.7)
446+
});
447+
448+
expect(scaleX.options.min).toBeCloseTo(1.5);
449+
expect(scaleX.options.max).toBeCloseTo(4);
450+
expect(scaleY.options.min).toBeCloseTo(1.2);
451+
expect(scaleY.options.max).toBeCloseTo(1.7);
452+
});
453+
});
454+
399455
describe('events', function() {
400456
it('should call onZoomStart, onZoom and onZoomComplete', function(done) {
401457
const startSpy = jasmine.createSpy('start');

0 commit comments

Comments
 (0)