-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[charts-pro] Zoom pointer improvements #17000
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 43 commits
b03ccf2
6d80993
ba8b61a
db75765
04f4a3a
7f6b43e
baccbf1
b4f0d24
48b461e
e38fcee
7988ff0
29923fa
6abb966
7ebb091
637e193
6dad5f2
6a8ad69
989c284
d9b51cd
1cb800a
a019178
3c18086
61bf657
9595fac
223c9b1
568f7da
c89dabd
01899c7
f9a56a7
b93cbd1
73f39f3
8dbaf63
9b4933d
3996fa9
8b1b724
c6be367
8873928
0dece29
f595e0c
290e7f0
75872f4
ecafae6
c5f6c8e
cc9e738
0edea79
fd88fbe
02c1a29
1cccafa
c55a5e3
f1a0e33
220efd1
7fb1449
4c3bb03
aedde9a
4eb9075
d3e4d6b
fe32424
64ae3a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
import * as React from 'react'; | ||
import { expect } from 'chai'; | ||
import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils'; | ||
import { describeSkipIf, isJSDOM, testSkipIf } from 'test/utils/skipIf'; | ||
import { BarChartPro } from './BarChartPro'; | ||
|
||
describeSkipIf(isJSDOM)('<BarChartPro /> - Zoom', () => { | ||
const { render } = createRenderer(); | ||
|
||
const barChartProps = { | ||
series: [ | ||
{ | ||
data: [10, 20, 30, 40], | ||
}, | ||
], | ||
xAxis: [ | ||
{ | ||
scaleType: 'band', | ||
data: ['A', 'B', 'C', 'D'], | ||
zoom: true, | ||
height: 30, | ||
id: 'x', | ||
}, | ||
], | ||
yAxis: [{ position: 'none' }], | ||
width: 100, | ||
height: 130, | ||
margin: 0, | ||
slotProps: { tooltip: { trigger: 'none' } }, | ||
} as const; | ||
|
||
const options = { | ||
wrapper: ({ children }: { children?: React.ReactNode }) => ( | ||
<div style={{ width: 100, height: 130 }}>{children}</div> | ||
), | ||
}; | ||
|
||
// eslint-disable-next-line mocha/no-top-level-hooks | ||
beforeEach(() => { | ||
// TODO: Remove beforeEach/afterEach after vitest becomes our main runner | ||
if (window?.document?.body?.style) { | ||
window.document.body.style.margin = '0'; | ||
} | ||
}); | ||
|
||
// eslint-disable-next-line mocha/no-top-level-hooks | ||
afterEach(() => { | ||
if (window?.document?.body?.style) { | ||
window.document.body.style.margin = '8px'; | ||
} | ||
}); | ||
|
||
it('should zoom on wheel', async () => { | ||
const { user } = render(<BarChartPro {...barChartProps} />, options); | ||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
|
||
const svg = document.querySelector('svg')!; | ||
|
||
await user.pointer([ | ||
{ | ||
target: svg, | ||
coords: { x: 50, y: 50 }, | ||
}, | ||
]); | ||
|
||
// scroll, we scroll exactly in the center of the svg | ||
// And we do it 200 times which is the lowest number to trigger a zoom where both A and D are not visible | ||
for (let i = 0; i < 200; i += 1) { | ||
fireEvent.wheel(svg, { deltaY: -1, clientX: 50, clientY: 50 }); | ||
} | ||
|
||
expect(screen.queryByText('A')).to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
|
||
// scroll back | ||
for (let i = 0; i < 200; i += 1) { | ||
fireEvent.wheel(svg, { deltaY: 1, clientX: 50, clientY: 50 }); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If as you said 200 is the limit to only see B and C, then just a few scroll back should be enough |
||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
}); | ||
|
||
['MouseLeft', 'TouchA'].forEach((pointerName) => { | ||
it(`should pan on ${pointerName} drag`, async () => { | ||
const { user } = render( | ||
<BarChartPro {...barChartProps} initialZoom={[{ axisId: 'x', start: 75, end: 100 }]} />, | ||
options, | ||
); | ||
|
||
expect(screen.queryByText('A')).to.equal(null); | ||
expect(screen.queryByText('B')).to.equal(null); | ||
expect(screen.queryByText('C')).to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
|
||
const svg = document.querySelector('svg')!; | ||
|
||
// we drag one position so C should be visible | ||
await user.pointer([ | ||
{ | ||
keys: `[${pointerName}>]`, | ||
target: svg, | ||
coords: { x: 5, y: 20 }, | ||
}, | ||
{ | ||
pointerName: pointerName === 'MouseLeft' ? undefined : pointerName, | ||
target: svg, | ||
coords: { x: 100, y: 20 }, | ||
}, | ||
{ | ||
keys: `[/${pointerName}]`, | ||
target: svg, | ||
coords: { x: 100, y: 20 }, | ||
}, | ||
]); | ||
|
||
expect(screen.queryByText('A')).to.equal(null); | ||
expect(screen.queryByText('B')).to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
|
||
// we drag all the way to the left so A should be visible | ||
await user.pointer([ | ||
{ | ||
keys: `[${pointerName}>]`, | ||
target: svg, | ||
coords: { x: 5, y: 20 }, | ||
}, | ||
{ | ||
pointerName: pointerName === 'MouseLeft' ? undefined : pointerName, | ||
target: svg, | ||
coords: { x: 300, y: 20 }, | ||
}, | ||
{ | ||
keys: `[/${pointerName}]`, | ||
target: svg, | ||
coords: { x: 300, y: 20 }, | ||
}, | ||
]); | ||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).to.equal(null); | ||
expect(screen.queryByText('C')).to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
}); | ||
}); | ||
|
||
// Technically it should work, but it's not working in the test environment | ||
// https://github.com/pmndrs/use-gesture/discussions/430 | ||
testSkipIf(true)('should zoom on pinch', async () => { | ||
const { user } = render(<BarChartPro {...barChartProps} />, options); | ||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
|
||
const svg = document.querySelector('svg')!; | ||
|
||
await user.pointer({ | ||
keys: '[TouchA]', | ||
target: svg, | ||
coords: { x: 50, y: 50 }, | ||
}); | ||
|
||
await user.pointer([ | ||
{ | ||
keys: '[TouchA>]', | ||
target: svg, | ||
coords: { x: 55, y: 45 }, | ||
}, | ||
{ | ||
keys: '[TouchB>]', | ||
target: svg, | ||
coords: { x: 45, y: 55 }, | ||
}, | ||
{ | ||
pointerName: 'TouchA', | ||
target: svg, | ||
coords: { x: 75, y: 25 }, | ||
}, | ||
{ | ||
pointerName: 'TouchB', | ||
target: svg, | ||
coords: { x: 25, y: 75 }, | ||
}, | ||
{ | ||
keys: '[/TouchA]', | ||
target: svg, | ||
coords: { x: 75, y: 25 }, | ||
}, | ||
{ | ||
keys: '[/TouchB]', | ||
target: svg, | ||
coords: { x: 25, y: 75 }, | ||
}, | ||
]); | ||
|
||
expect(screen.queryByText('A')?.textContent).to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import * as React from 'react'; | ||
import { expect } from 'chai'; | ||
import { createRenderer, screen, fireEvent } from '@mui/internal-test-utils'; | ||
import { describeSkipIf, isJSDOM } from 'test/utils/skipIf'; | ||
import { LineChartPro } from './LineChartPro'; | ||
|
||
describeSkipIf(isJSDOM)('<LineChartPro /> - Zoom', () => { | ||
const { render } = createRenderer(); | ||
|
||
const lineChartProps = { | ||
series: [ | ||
{ | ||
data: [10, 20, 30, 40], | ||
}, | ||
], | ||
xAxis: [ | ||
{ | ||
scaleType: 'point', | ||
data: ['A', 'B', 'C', 'D'], | ||
zoom: true, | ||
height: 30, | ||
id: 'x', | ||
}, | ||
], | ||
yAxis: [{ position: 'none' }], | ||
width: 100, | ||
height: 130, | ||
margin: 5, | ||
slotProps: { tooltip: { trigger: 'none' } }, | ||
} as const; | ||
|
||
const options = { | ||
wrapper: ({ children }: { children?: React.ReactNode }) => ( | ||
<div style={{ width: 100, height: 130 }}>{children}</div> | ||
), | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the advantage of the wrapper compared to just providing the width/height to the chart props? |
||
|
||
// eslint-disable-next-line mocha/no-top-level-hooks | ||
beforeEach(() => { | ||
// TODO: Remove beforeEach/afterEach after vitest becomes our main runner | ||
if (window?.document?.body?.style) { | ||
window.document.body.style.margin = '0'; | ||
} | ||
}); | ||
|
||
// eslint-disable-next-line mocha/no-top-level-hooks | ||
afterEach(() => { | ||
if (window?.document?.body?.style) { | ||
window.document.body.style.margin = '8px'; | ||
} | ||
}); | ||
|
||
it('should zoom on wheel', async () => { | ||
const { user } = render(<LineChartPro {...lineChartProps} />, options); | ||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
|
||
const svg = document.querySelector('svg')!; | ||
|
||
await user.pointer([ | ||
{ | ||
target: svg, | ||
coords: { x: 50, y: 50 }, | ||
}, | ||
]); | ||
|
||
// scroll, we scroll exactly in the center of the svg | ||
// And we do it 200 times which is the lowest number to trigger a zoom where both A and D are not visible | ||
for (let i = 0; i < 200; i += 1) { | ||
fireEvent.wheel(svg, { deltaY: -1, clientX: 50, clientY: 50 }); | ||
} | ||
|
||
expect(screen.queryByText('A')).to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
|
||
// scroll back | ||
for (let i = 0; i < 200; i += 1) { | ||
fireEvent.wheel(svg, { deltaY: 1, clientX: 50, clientY: 50 }); | ||
} | ||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).not.to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
}); | ||
|
||
['MouseLeft', 'TouchA'].forEach((pointerName) => { | ||
it(`should pan on ${pointerName} drag`, async () => { | ||
const { user } = render( | ||
<LineChartPro {...lineChartProps} initialZoom={[{ axisId: 'x', start: 75, end: 100 }]} />, | ||
options, | ||
); | ||
|
||
expect(screen.queryByText('A')).to.equal(null); | ||
expect(screen.queryByText('B')).to.equal(null); | ||
expect(screen.queryByText('C')).to.equal(null); | ||
expect(screen.queryByText('D')).not.to.equal(null); | ||
|
||
const svg = document.querySelector('svg')!; | ||
|
||
// we drag one position so C should be visible | ||
await user.pointer([ | ||
{ | ||
keys: `[${pointerName}>]`, | ||
target: svg, | ||
coords: { x: 5, y: 20 }, | ||
}, | ||
{ | ||
pointerName: pointerName === 'MouseLeft' ? undefined : pointerName, | ||
target: svg, | ||
coords: { x: 100, y: 20 }, | ||
}, | ||
{ | ||
keys: `[/${pointerName}]`, | ||
target: svg, | ||
coords: { x: 100, y: 20 }, | ||
}, | ||
]); | ||
|
||
expect(screen.queryByText('A')).to.equal(null); | ||
expect(screen.queryByText('B')).to.equal(null); | ||
expect(screen.queryByText('C')).not.to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
|
||
// we drag all the way to the left so A should be visible | ||
await user.pointer([ | ||
{ | ||
keys: `[${pointerName}>]`, | ||
target: svg, | ||
coords: { x: 5, y: 20 }, | ||
}, | ||
{ | ||
pointerName: pointerName === 'MouseLeft' ? undefined : pointerName, | ||
target: svg, | ||
coords: { x: 300, y: 20 }, | ||
}, | ||
{ | ||
keys: `[/${pointerName}]`, | ||
target: svg, | ||
coords: { x: 300, y: 20 }, | ||
}, | ||
]); | ||
|
||
expect(screen.queryByText('A')).not.to.equal(null); | ||
expect(screen.queryByText('B')).to.equal(null); | ||
expect(screen.queryByText('C')).to.equal(null); | ||
expect(screen.queryByText('D')).to.equal(null); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's surprising that it can not be done with
delatY: -200