MyPage
단순히 사용자가 영어 공부를 하고 문제를 푸는 단순한 과정만 있어서
학습한 데이터를 분석해서 제공하는 MyPage를 구현하게 되었다.
이번 글에서는 학습 시간 및 풀이 시간과 학습 레벨을 시각화하는 내용을 작성했다.
추후에는 더 다양한 분석 데이터를 추가해 MyPage를 확장할 계획이다.
학습 시간과 풀이 시간
DB
사용자가 학습과 퀴즈 풀이를 진행한 시간을 저장하기 위해 StudyHistory
테이블을 생성했다.
1
2
3
4
5
6
7
8
create table study_history(
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
study_time BIGINT,
quiz_time BIGINT,
date DATE ,
foreign key (user_id) REFERENCES user (id) on delete cascade
)
db 저장 방식
사용자가 학습하고 문제를 푸는 시간을 초 단위로 측정하여 DB에 저장했다.
(front에서는 이를 분 단위로 변환하여 표시한다.)
Repository
1
2
@Query("select s from StudyHistory s where s.date between :start and :end and s.user.id=:userId")
List<StudyHistory> findBy7daysTime(LocalDate start, LocalDate end, Long userId);
지난 일주일 동안의 학습 및 풀이 시간을 표시했다.
Front
1. 데이터 초기화
1
2
let studyData= Array(7).fill(0);
let quizData= Array(7).fill(0);
학습시간을 모아둔 배열을 studyData, 풀이 시간을 모아둔 배열을 quizData로 설정
사용자의 학습 및 풀이 시간이 없는 경우를 대비해 기본 값을 0으로 채웠다.
데이터 매핑 및 변환
1
2
3
4
5
6
7
const weekDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const startIndex = today.getDay(); // 오늘의 요일 인덱스 (0: Sun ~ 6: Sat)
for(i=0; i<response.length; i++){
const x = new Date(response[i].date).getDay(); // 요일 index
studyData.splice(x,1,((response[i].study_time)/60).toFixed(1)); // 초 → 분 변환
quizData.splice(x,1,((response[i].quiz_time)/60).toFixed(1));
}
요일별 데이터(시간)를 배열에 삽입하기 위해 splice()
를 활용했으며,
소수점 첫번째까지 나타내기 위해 toFixed()
를 사용했다.
*splice()
는 배열의 기존 요소를 삭제 또는 교체하거나 새 요소를 추가하여 배열의 내용을 변경한다.
요일의 인덱스에 맞춰서 일요일일 경우 index는 0이므로 배열의 0번째 값에 시간을 넣는것이다.
여기서 값을 대체 하기 위해 deleteCount을 1로 설정했다.
(0으로 설정하면 값이 교체가 아니라 추가가 된다.)
splice(start: number, deleteCount: number, ...items: T[])
3. 요일 재정렬
1
2
3
4
// 오늘 날짜가 가장 마지막으로 띄우게 재정렬
const orderedWeekDays = [...weekDays.slice(startIndex + 1), ...weekDays.slice(0, startIndex + 1)];
studyData = [...studyData.slice(startIndex + 1), ...studyData.slice(0, startIndex + 1)];
quizData = [...quizData.slice(startIndex + 1), ...quizData.slice(0, startIndex + 1)];
오늘이 만약 월요일이라면 가장 첫번째 데이터는 일요일이어야 한다.
4. 차트 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data: {
labels: orderedWeekDays,
datasets: [
{
label: '학습 시간 (분)',
data: studyData,
backgroundColor: 'rgba(75, 192, 192, 0.5)',
},
{
label: '풀이 시간 (분)',
data: quizData,
backgroundColor: 'rgba(255, 99, 132, 0.5)',
}
]
},
학습 Level
사용자가 학습한 예문들의 레벨을 조회하여 각 레벨별 비율을 시각적으로 나타냈다.
Repository
1
2
@Query("select new com.eng.dto.LevelResponseDto(c.level, count(c.level)) from Sentence c where c.id in (select s.sentence.id from Study s where s.user.id = :userId) group by c.level")
List<LevelResponseDto> findLevelByUserId(Long userId);
2개 이상 Object 배열을 클래스에서 가져올 때 Mapping에 대한 정보가 없기 때문에 변환이 되지 않아
No converter found capable of converting from type 라는 오류가 떴다.
DTO를 Query 내부에서 명시적으로 생성하여 객체 변환 오류를 방지했다.
front
1. 데이터 정렬 및 변환
1
2
3
4
5
6
7
8
9
const sum = response[0].cnt+response[1].cnt + response[2].cnt;
response.sort( (prev, curr) => {
if(prev.level>curr.level) return 1;
if(prev.level<curr.level) return -1;
})
const count = response.map(function (e) {
return (e.cnt/sum*100).toFixed(1);
})
0, 1, 2 순서로 ‘Easy’, ‘Medium’, ‘Hard’를 나타내기 위해 정렬했다.
level 별 개수만 배열에 담아서 퍼센트로 나타냈다.(소수점 첫째짜리)
2. 결과 시각화
1
2
3
4
5
6
7
data: {
labels: ['Easy', 'Medium', 'Hard'],
datasets: [{
data: count,
backgroundColor: ['#FF9999', '#FFCC99', '#99CCFF']
}]
}
REFERENCE