Skip to content

Commit 328ea55

Browse files
authored
[charts-pro] Fix wheel zoom clamping with custom minStart/maxEnd (#22159)
1 parent e590dfe commit 328ea55

2 files changed

Lines changed: 73 additions & 3 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { zoomAtPoint } from './useZoom.utils';
2+
3+
describe('zoomAtPoint', () => {
4+
const defaultOptions = {
5+
axisId: 'x',
6+
axisDirection: 'x' as const,
7+
minStart: 0,
8+
maxEnd: 100,
9+
minSpan: 10,
10+
maxSpan: 100,
11+
step: 5,
12+
panning: true,
13+
filterMode: 'keep' as const,
14+
reverse: false,
15+
slider: { enabled: false, preview: false, size: 0, showTooltip: 'hover' as const },
16+
};
17+
18+
describe('with default minStart/maxEnd (0/100)', () => {
19+
it('should zoom in around center', () => {
20+
const [min, max] = zoomAtPoint(0.5, 1.5, { axisId: '1', start: 20, end: 80 }, defaultOptions);
21+
expect(min).to.be.closeTo(30, 0.01);
22+
expect(max).to.be.closeTo(70, 0.01);
23+
});
24+
25+
it('should clamp to bounds when zooming out past limits', () => {
26+
const result = zoomAtPoint(0.5, 0.2, { axisId: '1', start: 30, end: 70 }, defaultOptions);
27+
expect(result).to.deep.equal([0, 100]);
28+
});
29+
});
30+
31+
describe('with custom minStart/maxEnd', () => {
32+
const customOptions = { ...defaultOptions, minStart: 20, maxEnd: 80 };
33+
34+
it('should respect custom minStart when zooming out hits lower bound', () => {
35+
// zoom={40,60}, scaleRatio=0.3, centerRatio=0.8 pushes newMinRange below 20
36+
const [min, max] = zoomAtPoint(0.8, 0.3, { axisId: '1', start: 40, end: 60 }, customOptions);
37+
expect(min).to.equal(20);
38+
expect(max).to.be.closeTo(80, 0.01);
39+
});
40+
41+
it('should respect custom maxEnd when zooming out hits upper bound', () => {
42+
const [min, max] = zoomAtPoint(0.2, 0.3, { axisId: '1', start: 40, end: 60 }, customOptions);
43+
expect(min).to.be.closeTo(20, 0.01);
44+
expect(max).to.equal(80);
45+
});
46+
47+
it('should clamp to [minStart, maxEnd] when zooming out past both limits', () => {
48+
const result = zoomAtPoint(0.5, 0.3, { axisId: '1', start: 40, end: 60 }, customOptions);
49+
expect(result).to.deep.equal([20, 80]);
50+
});
51+
52+
it('should not go below custom minStart', () => {
53+
const [min] = zoomAtPoint(0.5, 0.1, { axisId: '1', start: 40, end: 60 }, customOptions);
54+
expect(min).to.be.greaterThanOrEqual(20);
55+
});
56+
57+
it('should not go above custom maxEnd', () => {
58+
const [, max] = zoomAtPoint(0.5, 0.1, { axisId: '1', start: 40, end: 60 }, customOptions);
59+
expect(max).to.be.lessThanOrEqual(80);
60+
});
61+
62+
it('should preserve span when zooming in within bounds', () => {
63+
const [min, max] = zoomAtPoint(0.5, 1.5, { axisId: '1', start: 40, end: 60 }, customOptions);
64+
// new span = 20 / 1.5 ≈ 13.33
65+
expect(max - min).to.be.closeTo(13.33, 0.01);
66+
// center preserved at 50
67+
expect((min + max) / 2).to.be.closeTo(50, 0.01);
68+
});
69+
});
70+
});

packages/x-charts-pro/src/internals/plugins/useChartProZoom/gestureHooks/useZoom.utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ export const zoomAtPoint = (
2929
let maxSpillover = 0;
3030

3131
if (newMinRange < MIN_RANGE) {
32-
minSpillover = Math.abs(newMinRange);
32+
minSpillover = MIN_RANGE - newMinRange;
3333
newMinRange = MIN_RANGE;
3434
}
3535
if (newMaxRange > MAX_RANGE) {
36-
maxSpillover = Math.abs(newMaxRange - MAX_RANGE);
36+
maxSpillover = newMaxRange - MAX_RANGE;
3737
newMaxRange = MAX_RANGE;
3838
}
3939

@@ -45,7 +45,7 @@ export const zoomAtPoint = (
4545
newMinRange -= maxSpillover;
4646

4747
newMinRange = Math.min(MAX_RANGE - MIN_ALLOWED_SPAN, Math.max(MIN_RANGE, newMinRange));
48-
newMaxRange = Math.max(MIN_ALLOWED_SPAN, Math.min(MAX_RANGE, newMaxRange));
48+
newMaxRange = Math.max(MIN_RANGE + MIN_ALLOWED_SPAN, Math.min(MAX_RANGE, newMaxRange));
4949

5050
return [newMinRange, newMaxRange];
5151
};

0 commit comments

Comments
 (0)