-
Notifications
You must be signed in to change notification settings - Fork 2
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
[FE] feat: 리뷰 모아보기에서 질문별 통계 차트 구현 #803
Changes from 1 commit
6697bae
04450f5
ecd84ec
e87ac79
6525223
2741550
09b6aab
b609099
9d858da
35f66c5
d501de8
074f519
44b9f67
7ec01ab
e2544a4
7350b6e
b4b3033
fd088ac
76b2141
3026dbe
3095e1c
27c5286
b3bfb91
d984f19
424e3cc
8590919
41ead48
037e423
9c0ffd1
34a25a7
e9e6737
0ab8d0a
2b83d32
2a7f900
c429a53
76312d7
fbc97c7
c752e50
90a1f61
82749ef
63ff584
281f5bf
ac98ba4
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 |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import { useEffect, useState } from 'react'; | ||
|
||
import { ReviewVotes } from '@/types'; | ||
|
||
import generateGradientColors from '../../utils/generateGradientColors'; | ||
|
@@ -13,6 +15,8 @@ const DOUGHNUT_COLOR = { | |
const DoughnutChart = ({ reviewVotes }: { reviewVotes: ReviewVotes[] }) => { | ||
const radius = 90; // 차트의 반지름 | ||
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. svg를 위한 상수들인 것 같아, 같이 변경되지 않는 변수들은 대문자로 변수명을 변경하는 것 어떨가요? 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. 상수 정의... 접수완료... |
||
const circumference = 2 * Math.PI * radius; // 차트의 둘레 | ||
const centerX = 125; // svg의 중앙 좌표 (x) | ||
const centerY = 125; // svg의 중앙 좌표 (y) | ||
|
||
const total = reviewVotes.reduce((acc, reviewVote) => acc + reviewVote.count, 0); | ||
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. total, ratio. acc 라는 변수명이 구체적이었으면 해요. 무엇에 대한 총합인지, 어떤 것에 대한 비율인지, 계산한 결과값이 무엇을 의미하는 지가 변수에 담겼으면 해요 |
||
const ratios = reviewVotes.map((reviewVote) => reviewVote.count / total); | ||
|
@@ -29,32 +33,79 @@ const DoughnutChart = ({ reviewVotes }: { reviewVotes: ReviewVotes[] }) => { | |
// 색상 시작 및 끝값 정의 | ||
const colors = generateGradientColors(reviewVotes.length, DOUGHNUT_COLOR.START, DOUGHNUT_COLOR.END); | ||
|
||
// 애니메이션 상태 관리 | ||
const [animateIndex, setAnimateIndex] = useState(0); | ||
|
||
// 애니메이션 트리거 설정 | ||
useEffect(() => { | ||
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. 애니메이션 구현하느라 고생했어요. 고생한것 같아 말하지 미안하지만, 애니메이션의 필요성에 대해서는 느끼지 못하겠어요. 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. 저도 애니메이션을 구현하면서 필요성에 대해 고민해 봤는데요. 생각해보니 아코디언이 펼쳐지면서 차트에도 애니메이션이 실행되니 다소 피로할 수 있을 것 같다는 느낌이 드네요. 더군다나 아코디언을 클릭할때마다 애니메이션이 실행되니까 충분히 피로할 수 있을 것 같아요. 일단, 애니메이션... Bye.... |
||
if (animateIndex < reviewVotes.length - 1) { | ||
const timer = setTimeout(() => { | ||
setAnimateIndex(animateIndex + 1); // 다음 애니메이션 트리거 | ||
}, 40); | ||
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. 추후 40도 상수화하면 좋을 것 같아요~~ |
||
return () => clearTimeout(timer); | ||
} | ||
}, [animateIndex, reviewVotes.length]); | ||
|
||
// 각 조각의 중심 좌표를 계산하는 함수 | ||
const calculateLabelPosition = (startAngle: number, endAngle: number) => { | ||
const midAngle = (startAngle + endAngle) / 2; // 중간 각도 | ||
const labelRadius = radius * 1; // 텍스트가 배치될 반지름 (차트 내부) | ||
const x = centerX + labelRadius * Math.cos((midAngle * Math.PI) / 180); | ||
const y = centerY + labelRadius * Math.sin((midAngle * Math.PI) / 180); | ||
return { x, y }; | ||
}; | ||
|
||
return ( | ||
<S.DoughnutChartContianer> | ||
<S.DoughnutChartContainer> | ||
<svg viewBox="0 0 250 250" width="250" height="250"> | ||
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. vieBox, width, height도 상수화하면 좋겠네요 |
||
{reviewVotes.map((reviewVote, index) => { | ||
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. return 반환값안에서 svg를 생성하기 위해 계산하는 코드들이 있으니 가독성이 좋지 않아요. 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. 구현할 때는 몰랐는데 코드를 다시 보니.... 와우.... 가독성이 정말 좋지 않네요...^^ 추후에 컴포넌트로 분리하겠습니다! |
||
const ratio = reviewVote.count / total; | ||
const fillSpace = circumference * ratio; | ||
const emptySpace = circumference - fillSpace; | ||
const offset = (acc[index] / total) * circumference; | ||
|
||
// 시작 각도와 끝 각도를 계산 | ||
const startAngle = (acc[index] / total) * 360 + 90; | ||
const endAngle = ((acc[index] + reviewVote.count) / total) * 360 - 90; | ||
|
||
// 비율 레이블의 위치를 계산 | ||
const { x, y } = calculateLabelPosition(startAngle, endAngle); | ||
|
||
return ( | ||
<circle | ||
key={index} | ||
cx="125" // 중앙에 배치 | ||
cy="125" | ||
r={radius} | ||
fill="none" | ||
stroke={colors[index]} | ||
strokeWidth="65" | ||
strokeDasharray={`${fillSpace} ${emptySpace}`} // 조각의 길이와 나머지 길이 설정 | ||
strokeDashoffset={-offset} // 시작 위치 설정 | ||
/> | ||
<g key={index}> | ||
<circle | ||
cx={centerX} // 중앙에 배치 | ||
cy={centerY} | ||
r={radius} | ||
fill="none" | ||
stroke={colors[index]} | ||
strokeWidth="65" | ||
strokeDasharray={`${fillSpace} ${emptySpace}`} // 조각의 길이와 나머지 길이 설정 | ||
strokeDashoffset={-offset} // 시작 위치 설정 | ||
style={{ | ||
transition: 'stroke-dasharray 1s ease', // 애니메이션 추가 | ||
opacity: index <= animateIndex ? 1 : 0, // 해당 인덱스까지의 애니메이션만 보여줌 | ||
}} | ||
/> | ||
<text | ||
x={x} | ||
y={y} | ||
textAnchor="middle" | ||
dominantBaseline="middle" | ||
fontSize="14" | ||
opacity={index <= animateIndex ? 1 : 0} // 애니메이션과 함께 텍스트 보이기 | ||
style={{ | ||
transition: 'opacity 0.4s ease', // 텍스트의 투명도 애니메이션 | ||
}} | ||
> | ||
{Math.floor(ratios[index] * 100)}% | ||
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. 지금은 floor로 항상 버림 처리되어 각 퍼센트를 다 합쳤을 때 97%로, 오차가 꽤 있어요. 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. 아.. 예리하십니다 에프이! |
||
</text> | ||
</g> | ||
); | ||
})} | ||
</svg> | ||
<DoughnutChartDetails reviewVotes={reviewVotes} ratios={ratios} colors={colors} /> | ||
</S.DoughnutChartContianer> | ||
<DoughnutChartDetails reviewVotes={reviewVotes} colors={colors} /> | ||
</S.DoughnutChartContainer> | ||
); | ||
}; | ||
|
||
|
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.
사실상 상수 역할인 것 같은데 소문자로 선언한 이유가 있을까요?
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.
이유는 없습니다... 대문자로 변경하는 걸 잊었을 뿐.... 😭