프로젝트/할일목록(TODO) 앱

할일목록(TODO) 앱 5 - chart.js 로 그룹핑한 데이터 그래프로 그리기

syleemomo 2023. 8. 9. 01:34
728x90

https://www.chartjs.org/docs/latest/getting-started/

 

Getting Started | Chart.js

Getting Started Let's get started with Chart.js! Alternatively, see the example below or check samples. Create a Chart In this example, we create a bar chart for a single dataset and render it on an HTML page. Add this code snippet to your page: You should

www.chartjs.org

https://www.chartjs.org/docs/latest/general/colors.html

 

Colors | Chart.js

Colors Charts support three color options: for geometric elements, you can change background and border colors; for textual elements, you can change the font color. Also, you can change the whole canvas background. Default colors If a color is not specifie

www.chartjs.org

 

 

* 주의할점

토큰은 언제나 변경되므로 새로 발급받은 다음에 수업을 진행하도록 한다. 

반드시 관리자 토큰을 사용해야 한다.

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
        }
      }
    }
  })
})

chart.js - 전체 할일목록 카테고리별 그룹핑

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
        }
      }
    }
  })
})

chart.js - 전체 할일목록 할일종료별 그룹핑

<!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>

chart.js - 전체 할일목록 생성날짜별 그룹핑

<!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>

chart.js - 전체 할일목록 업데이트 날짜별 그룹핑

<!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>

chart.js - 전체 할일목록 종료날짜별 그룹핑

 

만약 날짜별 정렬이 제대로 되어 있지 않으면 프론트쪽 코드에서 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>

chart.js - 특정 사용자의 카테고리별 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/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>

chart.js - 특정 사용자의 할일종료 여부 그룹핑

 

* 특정 사용자의 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>

chart.js - 특정 사용자의 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/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>

특정 사용자의 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/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>

chart.js - 전체 할일목록 종료 날짜별 도넛 그래프

 

* 라인 그래프 그리기

<!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>

chart.js - 전체 할일목록 종료 날짜별 라인 그래프

 

* 년, 월 오름차순 정렬하기 

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'})
  }
}))

그룹핑 결과를 날짜별로 오름차순 정렬한다.

chart.js - 전체 할일목록 종료 날짜별 라인 그래프 (오름차순 정렬)

 

* 프로그램적으로 로그인후 그룹핑하기

랜덤으로 생성한 사용자의 이메일, 비밀번호를 몽고 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>

728x90