Skip to content

Commit 94ef0a4

Browse files
authored
[charts] Fix interaction performance (#16897)
1 parent 1b7c661 commit 94ef0a4

File tree

12 files changed

+208
-156
lines changed

12 files changed

+208
-156
lines changed

packages/x-charts-pro/src/FunnelChart/FunnelSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ const FunnelSection = consumeSlots(
3131
ref: React.Ref<SVGPathElement>,
3232
) {
3333
const { seriesId, dataIndex, classes, color, onClick, className, ...other } = props;
34-
const getInteractionItemProps = useInteractionItemProps();
34+
const interactionProps = useInteractionItemProps({ type: 'funnel', seriesId, dataIndex });
3535
const { isFaded, isHighlighted } = useItemHighlighted({
3636
seriesId,
3737
dataIndex,
3838
});
3939

4040
return (
4141
<FunnelSectionPath
42-
{...getInteractionItemProps({ type: 'funnel', seriesId, dataIndex })}
42+
{...interactionProps}
4343
filter={isHighlighted ? 'brightness(120%)' : undefined}
4444
opacity={isFaded ? 0.3 : 1}
4545
fill={color}

packages/x-charts-pro/src/Heatmap/HeatmapItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const useUtilityClasses = (ownerState: HeatmapItemOwnerState) => {
7676
function HeatmapItem(props: HeatmapItemProps) {
7777
const { seriesId, dataIndex, color, value, slotProps = {}, slots = {}, ...other } = props;
7878

79-
const getInteractionItemProps = useInteractionItemProps();
79+
const interactionProps = useInteractionItemProps({ type: 'heatmap', seriesId, dataIndex });
8080
const { isFaded, isHighlighted } = useItemHighlighted({
8181
seriesId,
8282
dataIndex,
@@ -95,7 +95,7 @@ function HeatmapItem(props: HeatmapItemProps) {
9595
const Cell = slots?.cell ?? HeatmapCell;
9696
const cellProps = useSlotProps({
9797
elementType: Cell,
98-
additionalProps: { ...getInteractionItemProps({ type: 'heatmap', seriesId, dataIndex }) },
98+
additionalProps: interactionProps,
9999
externalForwardedProps: { ...other },
100100
externalSlotProps: slotProps.cell,
101101
ownerState,

packages/x-charts/src/BarChart/BarElement.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function BarElement(props: BarElementProps) {
8787
onClick,
8888
...other
8989
} = props;
90-
const getInteractionItemProps = useInteractionItemProps();
90+
const interactionProps = useInteractionItemProps({ type: 'bar', seriesId: id, dataIndex });
9191
const { isFaded, isHighlighted } = useItemHighlighted({
9292
seriesId: id,
9393
dataIndex,
@@ -111,7 +111,7 @@ function BarElement(props: BarElementProps) {
111111
externalSlotProps: slotProps?.bar,
112112
externalForwardedProps: other,
113113
additionalProps: {
114-
...getInteractionItemProps({ type: 'bar', seriesId: id, dataIndex }),
114+
...interactionProps,
115115
style,
116116
onClick,
117117
cursor: onClick ? 'pointer' : 'unset',

packages/x-charts/src/ChartsTooltip/contentDisplayed.test.tsx

Lines changed: 53 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,19 @@ const config: Partial<BarChartProps> = {
2424
// | X X
2525
// | X X X X
2626
// ---A---B-
27+
//
28+
// Horizontal layout
29+
// A| X X X X
30+
// A| X X
31+
// B| X
32+
// B| X
33+
// --------
2734

2835
describe('ChartsTooltip', () => {
2936
const { render } = createRenderer();
37+
const wrapper = ({ children }: { children?: React.ReactNode }) => (
38+
<div style={{ width: 400, height: 400 }}>{children}</div>
39+
);
3040

3141
beforeEach(() => {
3242
// TODO: Remove beforeEach/afterEach after vitest becomes our main runner
@@ -45,21 +55,16 @@ describe('ChartsTooltip', () => {
4555
describeSkipIf(isJSDOM)('axis trigger', () => {
4656
it('should show right values with vertical layout on axis', async () => {
4757
const { user } = render(
48-
<div
49-
style={{
50-
width: 400,
51-
height: 400,
52-
}}
53-
>
54-
<BarChart
55-
{...config}
56-
series={[
57-
{ dataKey: 'v1', id: 's1', label: 'S1' },
58-
{ dataKey: 'v2', id: 's2', label: 'S2' },
59-
]}
60-
xAxis={[{ scaleType: 'band', dataKey: 'x' }]}
61-
/>
62-
</div>,
58+
<BarChart
59+
{...config}
60+
series={[
61+
{ dataKey: 'v1', id: 's1', label: 'S1' },
62+
{ dataKey: 'v2', id: 's2', label: 'S2' },
63+
]}
64+
xAxis={[{ scaleType: 'band', dataKey: 'x', position: 'none' }]}
65+
slotProps={{ tooltip: { trigger: 'axis' } }}
66+
/>,
67+
{ wrapper },
6368
);
6469
const svg = document.querySelector<HTMLElement>('svg')!;
6570

@@ -104,22 +109,17 @@ describe('ChartsTooltip', () => {
104109

105110
it('should show right values with horizontal layout on axis', async () => {
106111
const { user } = render(
107-
<div
108-
style={{
109-
width: 400,
110-
height: 400,
111-
}}
112-
>
113-
<BarChart
114-
{...config}
115-
layout="horizontal"
116-
series={[
117-
{ dataKey: 'v1', id: 's1', label: 'S1' },
118-
{ dataKey: 'v2', id: 's2', label: 'S2' },
119-
]}
120-
yAxis={[{ scaleType: 'band', dataKey: 'x' }]}
121-
/>
122-
</div>,
112+
<BarChart
113+
{...config}
114+
layout="horizontal"
115+
series={[
116+
{ dataKey: 'v1', id: 's1', label: 'S1' },
117+
{ dataKey: 'v2', id: 's2', label: 'S2' },
118+
]}
119+
yAxis={[{ scaleType: 'band', dataKey: 'x', position: 'none' }]}
120+
slotProps={{ tooltip: { trigger: 'axis' } }}
121+
/>,
122+
{ wrapper },
123123
);
124124
const svg = document.querySelector<HTMLElement>('svg')!;
125125

@@ -167,22 +167,16 @@ describe('ChartsTooltip', () => {
167167
describeSkipIf(isJSDOM)('item trigger', () => {
168168
it('should show right values with vertical layout on item', async () => {
169169
const { user } = render(
170-
<div
171-
style={{
172-
width: 400,
173-
height: 400,
174-
}}
175-
>
176-
<BarChart
177-
{...config}
178-
series={[
179-
{ dataKey: 'v1', id: 's1', label: 'S1' },
180-
{ dataKey: 'v2', id: 's2', label: 'S2' },
181-
]}
182-
xAxis={[{ scaleType: 'band', dataKey: 'x' }]}
183-
slotProps={{ tooltip: { trigger: 'item' } }}
184-
/>
185-
</div>,
170+
<BarChart
171+
{...config}
172+
series={[
173+
{ dataKey: 'v1', id: 's1', label: 'S1' },
174+
{ dataKey: 'v2', id: 's2', label: 'S2' },
175+
]}
176+
xAxis={[{ scaleType: 'band', dataKey: 'x', position: 'none' }]}
177+
slotProps={{ tooltip: { trigger: 'item' } }}
178+
/>,
179+
{ wrapper },
186180
);
187181
const rectangles = document.querySelectorAll<HTMLElement>('rect');
188182

@@ -205,30 +199,24 @@ describe('ChartsTooltip', () => {
205199

206200
it('should show right values with horizontal layout on item', async () => {
207201
const { user } = render(
208-
<div
209-
style={{
210-
width: 400,
211-
height: 400,
212-
}}
213-
>
214-
<BarChart
215-
{...config}
216-
series={[
217-
{ dataKey: 'v1', id: 's1', label: 'S1' },
218-
{ dataKey: 'v2', id: 's2', label: 'S2' },
219-
]}
220-
layout="horizontal"
221-
yAxis={[{ scaleType: 'band', dataKey: 'x' }]}
222-
slotProps={{ tooltip: { trigger: 'item' } }}
223-
/>
224-
</div>,
202+
<BarChart
203+
{...config}
204+
series={[
205+
{ dataKey: 'v1', id: 's1', label: 'S1' },
206+
{ dataKey: 'v2', id: 's2', label: 'S2' },
207+
]}
208+
layout="horizontal"
209+
yAxis={[{ scaleType: 'band', dataKey: 'x', position: 'none' }]}
210+
slotProps={{ tooltip: { trigger: 'item' } }}
211+
/>,
212+
{ wrapper },
225213
);
214+
226215
const rectangles = document.querySelectorAll<HTMLElement>('rect');
227216

228217
await user.pointer({
229218
target: rectangles[0],
230219
});
231-
232220
let cells = document.querySelectorAll<HTMLElement>('.MuiChartsTooltip-root td');
233221
expect([...cells].map((cell) => cell.textContent)).to.deep.equal(['', 'S1', '4']);
234222

packages/x-charts/src/LineChart/AreaElement.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ function AreaElement(props: AreaElementProps) {
101101
...other
102102
} = props;
103103

104-
const getInteractionItemProps = useInteractionItemProps();
104+
const interactionProps = useInteractionItemProps({ type: 'line', seriesId: id });
105105
const { isFaded, isHighlighted } = useItemHighlighted({
106106
seriesId: id,
107107
});
@@ -121,7 +121,7 @@ function AreaElement(props: AreaElementProps) {
121121
elementType: Area,
122122
externalSlotProps: slotProps?.area,
123123
additionalProps: {
124-
...getInteractionItemProps({ type: 'line', seriesId: id }),
124+
...interactionProps,
125125
onClick,
126126
cursor: onClick ? 'pointer' : 'unset',
127127
},

packages/x-charts/src/LineChart/CircleMarkElement.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import * as React from 'react';
33
import PropTypes from 'prop-types';
44
import { useTheme } from '@mui/material/styles';
5-
import { animated, useSpring } from '@react-spring/web';
5+
import { animated, useSpringValue } from '@react-spring/web';
66
import { useInteractionItemProps } from '../hooks/useInteractionItemProps';
77
import { useItemHighlighted } from '../hooks/useItemHighlighted';
88
import { MarkElementOwnerState, useUtilityClasses } from './markElementClasses';
@@ -49,15 +49,22 @@ function CircleMarkElement(props: CircleMarkElementProps) {
4949
} = props;
5050

5151
const theme = useTheme();
52-
const getInteractionItemProps = useInteractionItemProps();
52+
const interactionProps = useInteractionItemProps({ type: 'line', seriesId: id, dataIndex });
5353
const { isFaded, isHighlighted } = useItemHighlighted({
5454
seriesId: id,
5555
});
5656

5757
const store = useStore();
5858
const xAxisIdentifier = useSelector(store, selectorChartsInteractionXAxis);
5959

60-
const position = useSpring({ to: { x, y }, immediate: skipAnimation });
60+
const cx = useSpringValue(x, { immediate: skipAnimation });
61+
const cy = useSpringValue(y, { immediate: skipAnimation });
62+
63+
React.useEffect(() => {
64+
cy.start(y, { immediate: skipAnimation });
65+
cx.start(x, { immediate: skipAnimation });
66+
}, [cy, y, cx, x, skipAnimation]);
67+
6168
const ownerState = {
6269
id,
6370
classes: innerClasses,
@@ -71,16 +78,16 @@ function CircleMarkElement(props: CircleMarkElementProps) {
7178
<animated.circle
7279
{...other}
7380
// @ts-expect-error
74-
cx={position.x}
75-
cy={position.y}
81+
cx={cx}
82+
cy={cy}
7683
r={5}
7784
fill={(theme.vars || theme).palette.background.paper}
7885
stroke={color}
7986
strokeWidth={2}
8087
className={classes.root}
8188
onClick={onClick}
8289
cursor={onClick ? 'pointer' : 'unset'}
83-
{...getInteractionItemProps({ type: 'line', seriesId: id, dataIndex })}
90+
{...interactionProps}
8491
/>
8592
);
8693
}

packages/x-charts/src/LineChart/LineElement.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function LineElement(props: LineElementProps) {
100100
onClick,
101101
...other
102102
} = props;
103-
const getInteractionItemProps = useInteractionItemProps();
103+
const interactionProps = useInteractionItemProps({ type: 'line', seriesId: id });
104104
const { isFaded, isHighlighted } = useItemHighlighted({
105105
seriesId: id,
106106
});
@@ -120,7 +120,7 @@ function LineElement(props: LineElementProps) {
120120
elementType: Line,
121121
externalSlotProps: slotProps?.line,
122122
additionalProps: {
123-
...getInteractionItemProps({ type: 'line', seriesId: id }),
123+
...interactionProps,
124124
onClick,
125125
cursor: onClick ? 'pointer' : 'unset',
126126
},

packages/x-charts/src/LineChart/MarkElement.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as React from 'react';
33
import PropTypes from 'prop-types';
44
import { styled } from '@mui/material/styles';
55
import { symbol as d3Symbol, symbolsFill as d3SymbolsFill } from '@mui/x-charts-vendor/d3-shape';
6-
import { animated, to, useSpring } from '@react-spring/web';
6+
import { animated, to, useSpringValue } from '@react-spring/web';
77
import { getSymbol } from '../internals/getSymbol';
88
import { useInteractionItemProps } from '../hooks/useInteractionItemProps';
99
import { useItemHighlighted } from '../hooks/useItemHighlighted';
@@ -63,15 +63,22 @@ function MarkElement(props: MarkElementProps) {
6363
...other
6464
} = props;
6565

66-
const getInteractionItemProps = useInteractionItemProps();
66+
const interactionProps = useInteractionItemProps({ type: 'line', seriesId: id, dataIndex });
6767
const { isFaded, isHighlighted } = useItemHighlighted({
6868
seriesId: id,
6969
});
7070

7171
const store = useStore();
7272
const xAxisIdentifier = useSelector(store, selectorChartsInteractionXAxis);
7373

74-
const position = useSpring({ to: { x, y }, immediate: skipAnimation });
74+
const cx = useSpringValue(x, { immediate: skipAnimation });
75+
const cy = useSpringValue(y, { immediate: skipAnimation });
76+
77+
React.useEffect(() => {
78+
cy.start(y, { immediate: skipAnimation });
79+
cx.start(x, { immediate: skipAnimation });
80+
}, [cy, y, cx, x, skipAnimation]);
81+
7582
const ownerState = {
7683
id,
7784
classes: innerClasses,
@@ -85,16 +92,16 @@ function MarkElement(props: MarkElementProps) {
8592
<MarkElementPath
8693
{...other}
8794
style={{
88-
transform: to([position.x, position.y], (pX, pY) => `translate(${pX}px, ${pY}px)`),
89-
transformOrigin: to([position.x, position.y], (pX, pY) => `${pX}px ${pY}px`),
95+
transform: to([cx, cy], (pX, pY) => `translate(${pX}px, ${pY}px)`),
96+
transformOrigin: to([cx, cy], (pX, pY) => `${pX}px ${pY}px`),
9097
}}
9198
ownerState={ownerState}
9299
// @ts-expect-error
93100
className={classes.root}
94101
d={d3Symbol(d3SymbolsFill[getSymbol(shape)])()!}
95102
onClick={onClick}
96103
cursor={onClick ? 'pointer' : 'unset'}
97-
{...getInteractionItemProps({ type: 'line', seriesId: id, dataIndex })}
104+
{...interactionProps}
98105
/>
99106
);
100107
}

packages/x-charts/src/PieChart/PieArc.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ function PieArc(props: PieArcProps) {
104104
};
105105
const classes = useUtilityClasses(ownerState);
106106

107-
const getInteractionItemProps = useInteractionItemProps();
107+
const interactionProps = useInteractionItemProps({ type: 'pie', seriesId: id, dataIndex });
108108

109109
return (
110110
<PieArcRoot
@@ -131,7 +131,7 @@ function PieArc(props: PieArcProps) {
131131
strokeWidth={1}
132132
strokeLinejoin="round"
133133
{...other}
134-
{...getInteractionItemProps({ type: 'pie', seriesId: id, dataIndex })}
134+
{...interactionProps}
135135
/>
136136
);
137137
}

0 commit comments

Comments
 (0)