저번 포스팅 때, Legend에 hover했을 때, 해당 데이터만 highlight 되도록 구현을 했다.

이번에는 Legend를 커스터마이징하여 색상도 바꿔보도록 하려고한다.

1. CustomLegend 컴포넌트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const CustomLegend = (props: any) => {
const { payload, onMouseEnter, onMouseLeave } = props;

return (
<ul>
{payload.map((entry: any, index: any) => (
<li
key={`item-${index}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={{ listStyle: "none", color: colors.GRAPH[`${index + 1}`] }}
>
{entry.value}
</li>
))}
</ul>
);
};
  • 예시를 위해 타입은 any로 설정하였다.
  • Legend의 각 li에 mouse 이벤트를 할당하였다.
  • 마우스 이벤트는 호버된 데이터를 제외한 데이터들의 opacity를 줄여서 해당 데이터만 highlight 되도록 한다.

2. CustomLegend의 props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const handleMouseEnter = (o: any) => {
const dataKey = o.target.innerHTML;

const entries = Object.entries(opacity).map(([key, value]) =>
key === dataKey ? [key, 1] : [key, 0.2]
);

const mappedObj: any = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

그런데, 해당 데이터에 호버를 해도 모든 데이터의 opacity가 줄어드는 문제가 발생했다.

그 이유는 Legend에서의 props와 customLegend의 props가 달라서 mouse 이벤트가 잘못 동작했기 때문이다.

  • 이전 mouse 이벤트에서는 props안에 dataKey 속성으로 호버된 데이터 값을 가져올 수 있었다.
  • 하지만 customLegend에서는 props에 너무나도 많은 속성이 있었고 이 중 나는 target속성의 innerHTML 속성으로 호버된 데이터의 dataKey를 확인하는 로직을 구성하였다.

3. 전체 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import { useEffect, useState } from "react";
import {
LineChart,
Line,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";
import colors from "styles/colors";

const CustomLegend = (props: any) => {
const { payload, onMouseEnter, onMouseLeave } = props;

return (
<ul>
{payload.map((entry: any, index: any) => (
<li
key={`item-${index}`}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={{ listStyle: "none", color: colors.GRAPH[`${index + 1}`] }}
>
{entry.value}
</li>
))}
</ul>
);
};

function NewPortChart() {
const [opacity, setOpacity] = useState<any>({});

const handleMouseEnter = (o: any) => {
const dataKey = o.target.innerHTML;

const entries = Object.entries(opacity).map(([key, value]) =>
key === dataKey ? [key, 1] : [key, 0.2]
);

const mappedObj: any = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

const handleMouseLeave = () => {
const entries = Object.entries(opacity).map(([key, value]) => [key, 1]);

const mappedObj: any = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

useEffect(() => {
const mappedOpacity = Object.keys(data[0]).reduce((prev, curr) => {
prev = { ...prev, [curr]: 1 };
return prev;
}, {});
setOpacity(mappedOpacity);
}, []);

return (
<ResponsiveContainer width="100%" height="100%">
<LineChart width={857} height={440} data={data}>
<Tooltip />
<Legend
align="right"
verticalAlign="middle"
layout="vertical"
content={
<CustomLegend
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
}
/>
{Object.keys(data[0]).map((key, index) => (
<Line
key={key}
type="linear"
dataKey={key}
strokeOpacity={opacity[key]}
strokeLinecap="round"
stroke={colors.GRAPH[`${index + 1}`]}
activeDot={false}
dot={false}
/>
))}
</LineChart>
</ResponsiveContainer>
);
}

export default NewPortChart;

기본 CustomLegend

기본 customlegend

Hover된 데이터만 highlight

hover1
hover2
hover3

추가로…

색상만 바꿀 것이였다면 왜 CustomLegend까지 쓰면서 복잡하게 시도를 했을까 궁금증이 들 수도 있다.

디자이너 요구사항이 Legend와 해당 Line 데이터 끝 부분을 선으로 연결해달라는 요청이 있었기 때문에 CustomLegend를 사용해보았다.

아직 해당 부분은 좀 더 고민이 필요하기 때문에 추후에 포스팅하도록 하겠다.

댓글 공유

서비스가 주식 관련 서비스이다 보니 차트를 사용할 일이 잦다.

차트를 직접 구현하자니 너무 공수가 많이 들 것 같아 Recharts 라이브러리를 자주 사용하고 있다.

하지만 공식문서에서 모든게 나와있지 않아서 ChatGPT의 도움도 많이 받고 있다. 그래도 꽤 쓸만한 라이브러리이다.

오늘은 Recharts 라이브러리로 legend에 hover했을 때, hover된 데이터만 highlight 되도록 구현해볼 것이다.

chart

우선 기본적인 Line 차트를 렌더링한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { useState } from "react";
import {
LineChart,
Line,
XAxis,
YAxis,
Tooltip,
Legend,
ResponsiveContainer,
} from "recharts";

const data = [
{
name: "Page A",
uv: 4000,
pv: 2400,
amt: 2400,
},
{
name: "Page B",
uv: 3000,
pv: 1398,
amt: 2210,
},
{
name: "Page C",
uv: 2000,
pv: 9800,
amt: 2290,
},
{
name: "Page D",
uv: 2780,
pv: 3908,
amt: 2000,
},
{
name: "Page E",
uv: 1890,
pv: 4800,
amt: 2181,
},
{
name: "Page F",
uv: 2390,
pv: 3800,
amt: 2500,
},
{
name: "Page G",
uv: 3490,
pv: 4300,
amt: 2100,
},
];

function MultiLineCharts() {
const [opacity, setOpacity] = useState({ uv: 1, pv: 1 });

const handleMouseEnter = (o) => {
const { dataKey } = o;

const entries = Object.entries(opacity).map(([key, value]) =>
key === dataKey ? [key, 1] : [key, 0.2]
);

const mappedObj = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};

const handleMouseLeave = (o) => {
const { dataKey } = o;

const entries = Object.entries(opacity).map(([key, value]) => [key, 1]);

const mappedObj = entries.reduce((prev, curr) => {
const [key, value] = curr;
prev = { ...prev, [key]: value };
return prev;
}, {});

setOpacity(mappedObj);
};
return (
<ResponsiveContainer width="100%" height="100%">
<ResponsiveContainer width="100%" height={300}>
<LineChart
width={500}
height={300}
data={data}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5,
}}
>
<XAxis dataKey="name" />
<YAxis />
<Tooltip />
<Legend
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
<Line
type="monotone"
dataKey="pv"
strokeOpacity={opacity.pv}
stroke="#8884d8"
activeDot={{ r: 8 }}
/>
<Line
type="monotone"
dataKey="uv"
strokeOpacity={opacity.uv}
stroke="#82ca9d"
/>
</LineChart>
</ResponsiveContainer>
</ResponsiveContainer>
);
}

export default MultiLineCharts;
  • Line 그래프의 opacityuseState로 관리한다.
  • Legend에 onMouseEnter, onMouseLeave 이벤트를 할당한다.
  • 여기서 유저가 이벤트를 발생시킨 요소만 opacity를 두고 나머지 데이터들의 opacity를 줄여주기 위해 opacity 객체를 재구성했다.
  • Object.entries()reduce()를 사용하여 편리하게 객체를 재구성할 수 있다.

결과

hover chart

  • 보라색 Legend Hover 시

hover_chart2

  • 연두색 Legend Hover 시

댓글 공유

  • page 1 of 1

loco9939

author.bio


author.job