https://www.chartjs.org/docs/latest/getting-started/
https://www.chartjs.org/docs/latest/general/colors.html
* 주의할점
토큰은 언제나 변경되므로 새로 발급받은 다음에 수업을 진행하도록 한다.
반드시 관리자 토큰을 사용해야 한다.
CORS 에러가 나면 아래와 같이 서버쪽 코드를 변경해서 브라우저 요청(live server)을 허용하도록 한다.
const corsOptions = {
origin: '*',
credentials: true
}
* TODO 그룹핑 후 정렬하기 - 리팩토링
router.get('/group/:field', isAuth, isAdmin, expressAsyncHandler(async (req, res, next) => {
if(req.params.field === 'category' || req.params.field === 'isDone'){
const docs = await Todo.aggregate([
{
$group: {
_id: `$${req.params.field}`,
count: { $sum: 1 }
}
},
{ $sort : { _id : 1} }
])
console.log(`Number Of Group: , ${docs.length}`) // 그룹 갯수
// docs.sort((d1, d2) => d1._id - d2._id) // _id 기준으로 정렬
res.json({ code: 200, docs })
}else{
res.status(400).json({ code: 400, message: 'you gave wrong field to group documents !'})
}
}))
router.get('/group/mine/:field', isAuth, expressAsyncHandler(async (req, res, next) => { // 사용자 대쉬보드
if(req.params.field === 'category' || req.params.field === 'isDone'){
const docs = await Todo.aggregate([
{
$match: { author: new ObjectId(req.user._id) } // 내가 작성한 할일목록 필터링
},
{
$group: {
_id: `$${req.params.field}`,
count: { $sum: 1 }
}
},
{ $sort : { _id : 1} }
])
console.log(`Number Of Group: , ${docs.length}`) // 그룹 갯수
// docs.sort((d1, d2) => d1._id - d2._id) // _id 기준으로 정렬
res.json({ code: 200, docs })
}else{
res.status(400).json({ code: 400, message: 'you gave wrong field to group documents !'})
}
}))
router.get('/group/date/:field', isAuth, isAdmin, expressAsyncHandler(async (req, res, next) => {
if(req.params.field === 'createdAt' ||
req.params.field === 'lastModifiedAt' ||
req.params.field === 'finishedAt'){
const docs = await Todo.aggregate([
{
$group: {
_id: { year: { $year: `$${req.params.field}`}, month: { $month: `$${req.params.field}` }},
count: { $sum: 1 }
}
},
{ $sort : { _id : 1} }
])
console.log(`Number Of Group: ${docs.length}`) // 그룹 갯수
// docs.sort((d1, d2) => d1._id - d2._id)
res.json({ code: 200, docs})
}else{
res.status(400).json({ code: 400, message: 'you gave wrong field to group documents !' })
}
}))
router.get('/group/mine/date/:field', isAuth, expressAsyncHandler(async (req, res, next) => {
if(req.params.field === 'createdAt' ||
req.params.field === 'lastModifiedAt' ||
req.params.field === 'finishedAt'){
const docs = await Todo.aggregate([
{
$match: { author: new ObjectId(req.user._id) }
},
{
$group: {
_id: { year: { $year: `$${req.params.field}`}, month: { $month: `$${req.params.field}` }},
count: { $sum: 1 }
}
},
{ $sort : { _id : 1} }
])
console.log(`Number Of Group: ${docs.length}`) // 그룹 갯수
// docs.sort((d1, d2) => d1._id - d2._id)
res.json({ code: 200, docs})
}else{
res.status(400).json({ code: 400, message: 'you gave wrong field to group documents !' })
}
}))
server > src > routes > todos.js 파일의 그룹핑 코드에서 $group 파이프라인 이후 정렬을 위하여 $sort 파이프라인을 적용하는 코드를 추가하였다. _id 기준으로 정렬하기 위하여 위와 같이 코드를 수정하였다.
* 전체 할일목록 카테고리별 그룹핑
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NjM5N2E1YzliYTBmNzE1ZjFlZjZjNmUiLCJuYW1lIjoic3VuQGdtYWlsLmNvbSIsInVzZXJJZCI6InN1bnJpc2UiLCJpc0FkbWluIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDI0LTA1LTA3VDAwOjQ4OjI4LjM3N1oiLCJpYXQiOjE3MTUwNDMxNDksImV4cCI6MTcxNTEyOTU0OSwiaXNzIjoic3VucmlzZSJ9.2fBFKwlA_X5K-SVYXfxIAzpRy_xQcUCIbAZB22nolZw'
fetch('http://localhost:5000/api/todos/group/category', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}) // GET 방식의 요청
.then(res => res.json())
.then(data => {
const group = data.docs
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'bar',
data: {
labels: group.filter(item => item._id).map(item => item._id),
datasets: [{
label: '# of category',
data: group.filter(item => item._id).map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: false
}
}
}
})
})
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NjM5N2E1YzliYTBmNzE1ZjFlZjZjNmUiLCJuYW1lIjoic3VuQGdtYWlsLmNvbSIsInVzZXJJZCI6InN1bnJpc2UiLCJpc0FkbWluIjp0cnVlLCJjcmVhdGVkQXQiOiIyMDI0LTA1LTA3VDAwOjQ4OjI4LjM3N1oiLCJpYXQiOjE3MTUwNDMxNDksImV4cCI6MTcxNTEyOTU0OSwiaXNzIjoic3VucmlzZSJ9.2fBFKwlA_X5K-SVYXfxIAzpRy_xQcUCIbAZB22nolZw'
fetch('http://localhost:5000/api/todos/group/isDone', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}) // GET 방식의 요청
.then(res => res.json())
.then(data => {
const group = data.docs
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'bar',
data: {
labels: group.filter(item => item._id !== undefined && item._id !== null).map(item => item._id ? "종료" : "진행중"),
datasets: [{
label: '# of category',
data: group.filter(item => item._id !== undefined && item._id !== null).map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: false
}
}
}
})
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGNlMjVlNDc3ZjM5OTA4NjJkYTFhYTEiLCJuYW1lIjoi7YOc7JaRIiwiZW1haWwiOiJzdW5AZ21haWwuY29tIiwidXNlcklkIjoic3VucmlzZSIsImlzQWRtaW4iOnRydWUsImNyZWF0ZWRBdCI6IjIwMjMtMDgtMDVUMTA6MzU6MTYuNTU0WiIsImlhdCI6MTY5MTUxMjc3MywiZXhwIjoxNjkxNTk5MTczLCJpc3MiOiJzdW5yaXNlIn0.H9k4LaU0uKcfZ7UggBeSVLgmv_wSjbAS1YXXJs78arg'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/date/createdAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGNlMjVlNDc3ZjM5OTA4NjJkYTFhYTEiLCJuYW1lIjoi7YOc7JaRIiwiZW1haWwiOiJzdW5AZ21haWwuY29tIiwidXNlcklkIjoic3VucmlzZSIsImlzQWRtaW4iOnRydWUsImNyZWF0ZWRBdCI6IjIwMjMtMDgtMDVUMTA6MzU6MTYuNTU0WiIsImlhdCI6MTY5MTUxMjc3MywiZXhwIjoxNjkxNTk5MTczLCJpc3MiOiJzdW5yaXNlIn0.H9k4LaU0uKcfZ7UggBeSVLgmv_wSjbAS1YXXJs78arg'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/date/lastModifiedAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGNlMjVlNDc3ZjM5OTA4NjJkYTFhYTEiLCJuYW1lIjoi7YOc7JaRIiwiZW1haWwiOiJzdW5AZ21haWwuY29tIiwidXNlcklkIjoic3VucmlzZSIsImlzQWRtaW4iOnRydWUsImNyZWF0ZWRBdCI6IjIwMjMtMDgtMDVUMTA6MzU6MTYuNTU0WiIsImlhdCI6MTY5MTUxMjc3MywiZXhwIjoxNjkxNTk5MTczLCJpc3MiOiJzdW5yaXNlIn0.H9k4LaU0uKcfZ7UggBeSVLgmv_wSjbAS1YXXJs78arg'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/date/finishedAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
만약 날짜별 정렬이 제대로 되어 있지 않으면 프론트쪽 코드에서 then 메서드 안에서 아래 코드를 추가한다.
const group = data.docs
console.log(group)
group.sort((a, b) => {
if(a._id.year > b._id.year) return 1
else if(a._id.year < b._id.year) return -1
if(a._id.month > b._id.month) return 1
else if(a._id.month < b._id.month) return -1
else 0
})
// 차트 그리기
* 특정 사용자의 카테고리별 그룹핑
관리자가 아닌 특정 사용자의 토큰으로 교체해서 서버에 요청을 보내도록 한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGQyMzczMTA0Mzk4MmQ2YTBhOTkzYTYiLCJuYW1lIjoiaGVjYnoiLCJlbWFpbCI6Imx3dmJuZXhAZ21haWwuY29tIiwidXNlcklkIjoicGlldXhuZ3FiZCIsImlzQWRtaW4iOmZhbHNlLCJjcmVhdGVkQXQiOiIyMDIzLTA4LTA4VDEyOjM4OjA5LjY1MloiLCJpYXQiOjE2OTE1MDU5NTksImV4cCI6MTY5MTU5MjM1OSwiaXNzIjoic3VucmlzZSJ9.Rfv2hJsRM9uWwtpq7R3IpwEViHi7YYZsFz7GZguWq2A'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/mine/category', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => item._id),
datasets: [{
label: '# of Todos By',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFB1C1',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
* 특정 사용자의 할일종료 여부별 그룹핑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGQyMzczMTA0Mzk4MmQ2YTBhOTkzYTYiLCJuYW1lIjoiaGVjYnoiLCJlbWFpbCI6Imx3dmJuZXhAZ21haWwuY29tIiwidXNlcklkIjoicGlldXhuZ3FiZCIsImlzQWRtaW4iOmZhbHNlLCJjcmVhdGVkQXQiOiIyMDIzLTA4LTA4VDEyOjM4OjA5LjY1MloiLCJpYXQiOjE2OTE1MDU5NTksImV4cCI6MTY5MTU5MjM1OSwiaXNzIjoic3VucmlzZSJ9.Rfv2hJsRM9uWwtpq7R3IpwEViHi7YYZsFz7GZguWq2A'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/mine/isDone', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => item._id ? "종료": "진행중"),
datasets: [{
label: '# of Todos By',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFB1C1',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
* 특정 사용자의 TODO 생성날짜별 그룹핑
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGQyMzczMTA0Mzk4MmQ2YTBhOTkzYTYiLCJuYW1lIjoiaGVjYnoiLCJlbWFpbCI6Imx3dmJuZXhAZ21haWwuY29tIiwidXNlcklkIjoicGlldXhuZ3FiZCIsImlzQWRtaW4iOmZhbHNlLCJjcmVhdGVkQXQiOiIyMDIzLTA4LTA4VDEyOjM4OjA5LjY1MloiLCJpYXQiOjE2OTE1MDU5NTksImV4cCI6MTY5MTU5MjM1OSwiaXNzIjoic3VucmlzZSJ9.Rfv2hJsRM9uWwtpq7R3IpwEViHi7YYZsFz7GZguWq2A'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/mine/date/createdAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}월`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFB1C1',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGQyMzczMTA0Mzk4MmQ2YTBhOTkzYTYiLCJuYW1lIjoiaGVjYnoiLCJlbWFpbCI6Imx3dmJuZXhAZ21haWwuY29tIiwidXNlcklkIjoicGlldXhuZ3FiZCIsImlzQWRtaW4iOmZhbHNlLCJjcmVhdGVkQXQiOiIyMDIzLTA4LTA4VDEyOjM4OjA5LjY1MloiLCJpYXQiOjE2OTE1MDU5NTksImV4cCI6MTY5MTU5MjM1OSwiaXNzIjoic3VucmlzZSJ9.Rfv2hJsRM9uWwtpq7R3IpwEViHi7YYZsFz7GZguWq2A'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/mine/date/lastModifiedAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}월`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFB1C1',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGQyMzczMTA0Mzk4MmQ2YTBhOTkzYTYiLCJuYW1lIjoiaGVjYnoiLCJlbWFpbCI6Imx3dmJuZXhAZ21haWwuY29tIiwidXNlcklkIjoicGlldXhuZ3FiZCIsImlzQWRtaW4iOmZhbHNlLCJjcmVhdGVkQXQiOiIyMDIzLTA4LTA4VDEyOjM4OjA5LjY1MloiLCJpYXQiOjE2OTE1MDU5NTksImV4cCI6MTY5MTU5MjM1OSwiaXNzIjoic3VucmlzZSJ9.Rfv2hJsRM9uWwtpq7R3IpwEViHi7YYZsFz7GZguWq2A'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/mine/date/finishedAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'bar',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}월`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFB1C1',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
* 도넛 그래프 그리기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGNlMjVlNDc3ZjM5OTA4NjJkYTFhYTEiLCJuYW1lIjoi7YOc7JaRIiwiZW1haWwiOiJzdW5AZ21haWwuY29tIiwidXNlcklkIjoic3VucmlzZSIsImlzQWRtaW4iOnRydWUsImNyZWF0ZWRBdCI6IjIwMjMtMDgtMDVUMTA6MzU6MTYuNTU0WiIsImlhdCI6MTY5MTUxMjc3MywiZXhwIjoxNjkxNTk5MTczLCJpc3MiOiJzdW5yaXNlIn0.H9k4LaU0uKcfZ7UggBeSVLgmv_wSjbAS1YXXJs78arg'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/date/finishedAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'doughnut',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}일`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: ['orange', 'purple', 'skyblue', 'green' ]
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
* 라인 그래프 그리기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODO 분석그래프</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NGNlMjVlNDc3ZjM5OTA4NjJkYTFhYTEiLCJuYW1lIjoi7YOc7JaRIiwiZW1haWwiOiJzdW5AZ21haWwuY29tIiwidXNlcklkIjoic3VucmlzZSIsImlzQWRtaW4iOnRydWUsImNyZWF0ZWRBdCI6IjIwMjMtMDgtMDVUMTA6MzU6MTYuNTU0WiIsImlhdCI6MTY5MTUxMjc3MywiZXhwIjoxNjkxNTk5MTczLCJpc3MiOiJzdW5yaXNlIn0.H9k4LaU0uKcfZ7UggBeSVLgmv_wSjbAS1YXXJs78arg'
const ctx = document.getElementById('myChart');
fetch('http://127.0.0.1:5000/api/todos/group/date/finishedAt', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => res.json())
.then(data => {
const group = data.docs
console.log(group)
// 차트 그리기
new Chart(ctx, {
type: 'line',
data: {
labels: group.map(item => `${item._id.year}년 ${item._id.month}일`),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
borderColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
* 년, 월 오름차순 정렬하기
server > src > routes > todos.js 파일의 해당 부분을 아래와 같이 수정한다.
router.get('/group/date/:field', isAuth, expressAsyncHandler(async (req, res, next) => { // 어드민 페이지
if(!req.user.isAdmin){
res.status(401).json({ code: 401, message: 'You are not authorized to use this service !'})
}else{
if(req.params.field === 'createdAt' || req.params.field === 'lastModifiedAt' || req.params.field === 'finishedAt'){
const docs = await Todo.aggregate([
{
$group: {
_id: { year: { $year: `$${req.params.field}` }, month: { $month: `$${req.params.field}` } },
count: { $sum: 1 }
}
},
{ $sort : { _id : 1 } } // 날짜 오름차순 정렬
])
console.log(`Number Of Group: ${docs.length}`) // 그룹 갯수
docs.sort((d1, d2) => d1._id - d2._id)
res.json({ code: 200, docs})
}else{
res.status(204).json({ code: 204, message: 'No Content'})
}
}
}))
router.get('/group/mine/date/:field', isAuth, expressAsyncHandler(async (req, res, next) => { // 어드민 페이지
if(req.params.field === 'createdAt' || req.params.field === 'lastModifiedAt' || req.params.field === 'finishedAt'){
const docs = await Todo.aggregate([
{
$match: { author: new ObjectId(req.user._id) }
},
{
$group: {
_id: { year: { $year: `$${req.params.field}` }, month: { $month: `$${req.params.field}` } },
count: { $sum: 1 }
}
},
{ $sort : { _id : 1 } } // 날짜 오름차순 정렬
])
console.log(`Number Of Group: ${docs.length}`) // 그룹 갯수
docs.sort((d1, d2) => d1._id - d2._id)
res.json({ code: 200, docs})
}else{
res.status(204).json({ code: 204, message: 'No Content'})
}
}))
그룹핑 결과를 날짜별로 오름차순 정렬한다.
* 프로그램적으로 로그인후 그룹핑하기
랜덤으로 생성한 사용자의 이메일, 비밀번호를 몽고 DB 데이터베이스에서 찾아서 로그인하도록 한다.
const BASE_URL = '127.0.0.1:5000'
async function login(email, password){
const userJSON = await fetch(`http://${BASE_URL}/api/users/login`, {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
email, password
})
})
const user = await userJSON.json()
return user
}
async function getGroups(field, token){
const groupJSON = await fetch(`http://${BASE_URL}/api/todos/group/mine/${field}`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
})
const group = await groupJSON.json()
return group.docs
}
async function fetchData(){
const user = await login("enwrvuf@gmail.com", "ajtgnhhpesywh")
const group = await getGroups("category", user.token)
return group
}
fetchData()
.then(group => {
console.log(group)
// 차트 그리기
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'line',
data: {
labels: group.map(item => item._id),
datasets: [{
label: '# of Todos',
data: group.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
borderColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
* 관리자 사용자로 전체목록 그룹핑하고 그래프로 보여주기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
async function login(email, password){
const user = await fetch('http://127.0.0.1:5000/api/users/login', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
email, password
})
}).then(res => res.json())
return user
}
async function getGroups(field, token){
const group = await fetch(`http://127.0.0.1:5000/api/todos/group/${field}`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
}).then(res => res.json())
return group
}
async function fetchData(){
const user = await login("youna@gmail.com", "yona1234") // 로그인
let group = await getGroups('category', user.token)
group = group.docs
return group
}
fetchData()
.then(group => {
console.log(group)
// 차트 그리기
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'line',
data: {
labels: group.filter(item => item._id).map(item => item._id),
datasets: [{
label: '# of Todos',
data: group.filter(item => item._id).map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
borderColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
})
</script>
</body>
</html>
몽고디비에 10000개의 TODO 를 생성해서 전체 할일목록을 카테고리별로 그룹핑해보았다.
* 유니버셜 API 테스트 코드
const BASE_URL = 'http://127.0.0.1:5000' // 도메인 주소
const email = 'enwrvuf@gmail.com' // 사용자 연락처
const password = 'ajtgnhhpes123@' // 사용자 비밀번호
const graphType = 'line' // 그래프 종류
const field = 'createdAt' // 그룹핑 기준
async function login(email, password){
const userJSON = await fetch(`${BASE_URL}/api/users/login`, {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
email, password
})
})
const user = await userJSON.json()
return user
}
async function getGroups(field, user){
let base_url = `${BASE_URL}/api/todos/group`
if(!user.isAdmin){
base_url += '/mine'
}
if(field === 'createdAt' || field === 'lastModifiedAt' || field === 'finishedAt'){
base_url += '/date'
}
const groupJSON = await fetch(`${base_url}/${field}`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${user.token}`
}
})
const group = await groupJSON.json()
return group.docs
}
async function fetchData(email, password, field){
const user = await login(email, password) // 로그인
const group = await getGroups(field, user) // 그룹핑
return group
}
function displayChart(type, group){
const ctx = document.getElementById('myChart')
new Chart(ctx, {
type,
data: {
labels: group
.filter(item => item._id !== null && item._id !== undefined && item._id !== '')
.map(item => item._id.year ? `${item._id.year}년 ${item._id.month}월`
: typeof item._id === 'boolean' ? (item._id === true ? "종료" : "진행중")
: item._id),
datasets: [{
label: '# of Todos',
data: group
.filter(item => item._id !== null && item._id !== undefined && item._id !== '')
.map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
borderColor: '#FFD700',
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
}
fetchData(email, password, field)
.then(group => {
console.log(group)
displayChart(graphType, group)
})
* 가로방향 그래프 그리기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const email = "ycukkza@gmail.com" // 사용자 연락처
const password = "lvnqlofkwmddh" // 사용자 비밀번호
const graphType = 'bar' // 그래프 종류
const field = 'category' // 그룹핑 기준
async function login(email, password){
const user = await fetch('http://127.0.0.1:5000/api/users/login', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
email, password
})
}).then(res => res.json())
return user
}
async function getGroups(field, user){
let baseUrl = 'http://127.0.0.1:5000/api/todos/group'
if(!user.isAdmin){
baseUrl += '/mine'
}
if(field === 'createdAt' || field === 'lastModifiedAt' || field === 'finishedAt'){
baseUrl += '/date'
}
const group = await fetch(`${baseUrl}/${field}`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${user.token}`
}
}).then(res => res.json())
return group
}
async function fetchData(email, password, field){
const user = await login(email, password) // 로그인
let group = await getGroups(field, user)
group = group.docs
return group
}
function displayChart(type, group){
// 차트 그리기
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type,
data: {
labels: group.filter(item => item._id !== null && item._id !== undefined && item._id !== '').map(item => item._id.year ? `${item._id.year}년 ${item._id.month}월` : typeof item._id === 'boolean' ? (item._id === true ? "종료": "진행중") : item._id),
datasets: [{
label: '# of Todos',
data: group.filter(item => item._id !== null && item._id !== undefined && item._id !== '').map(item => item.count),
borderWidth: 1,
backgroundColor: '#FFD700',
borderColor: '#FFD700',
}]
},
options: {
indexAxis: 'y', // 가로방향 그래프
scales: {
y: {
beginAtZero: true
}
},
plugins: {
colors: {
enabled: true
}
}
}
});
}
fetchData(email, password, field)
.then(group => {
console.log(group)
displayChart(graphType, group)
})
</script>
</body>
</html>
'프로젝트 > 할일목록(TODO) 앱' 카테고리의 다른 글
할일목록(TODO) 앱 7 - Mongoose virtual 과 moment.js 로 현재 시각 기준으로 시간 표시하기 (0) | 2023.08.12 |
---|---|
할일목록(TODO) 앱 6 - 데이터 검증하기 (validation) (1) | 2023.08.11 |
할일목록(TODO) 앱 4 - API 설계 및 구현 (0) | 2021.10.05 |
할일목록(TODO) 앱 3 - Mongoose 로 데이터 모델 설계 및 구현하기 (0) | 2021.10.05 |
할일목록(TODO) 앱 2 - 기본적인 서버 설정 및 Mongo DB 연동 (0) | 2021.10.05 |