리스트에서 선택한 데이터만 📊 로 보여주기

AeZan·2021년 10월 18일
0
post-thumbnail
  1. 학생들의 이름, 국어 점수, 영어 점수, 수학 점수를 가진 테이블 작성
  2. 모든 학생들의 정보를 가져오는 쿼리문 1개
    -> JPQL 사용
    과목 별 평균을 가져오는 쿼리문 1개
    -> queryDSL 사용
    데이터 INSERT
    -> native sql
  3. 모든 학생들의 정보를 리스트 형태로 보여주고, 그 중 선택된 학생들의 점수만 가지고 평균값을 산출하여 차트로 보여줌
    chart.js 도 사용해볼 겸 해보는 연습용 프로젝트

📌 연습용 프로젝트 만들기

  • controller
@Controller
@RequiredArgsConstructor
public class MainController {

    @NonNull
    private final ScoreService scoreService;

    @GetMapping("/")
    public String main(Model model) {
        List<Score> studentList = scoreService.findAll();   //모든 학생 정보
        List<Tuple> scoreList = scoreService.findScoreAvgList();    //과목 별 평균 점수

        model.addAttribute("studentList", studentList);
        model.addAttribute("scoreList", scoreList);
        
        return "index";
    }

}
  • domain
@Getter
@Setter
@Entity
@Table(name = "score")
@NoArgsConstructor
public class Score {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer Id;

    @Column
    @NotNull
    private String name;

    @Range(min = 0)
    @Column(columnDefinition = "float default 0")
    private Integer korea;

    @Range(min = 0)
    @Column(columnDefinition = "float default 0")
    private Integer english;

    @Range(min = 0)
    @Column(columnDefinition = "float default 0")
    private Integer math;
}
  • repository : repository, customRepositoy, repositoryImpl
//repository
@Repository
public interface ScoreRepository extends JpaRepository<Score, Integer> {
}

//customRepository
@NoRepositoryBean
public interface CustomScoreRepository {
    List<Tuple> findScoreAvgList();
}

//repositoryImpl
public class ScoreRepositoryImpl extends QuerydslRepositorySupport implements CustomScoreRepository {

    public ScoreRepositoryImpl() {
        super(Score.class);
    }

    QScore score = QScore.score;

    @Override
    public List<Tuple> findScoreAvgList() {
        return from(score).select(score.korea.avg(), score.english.avg(), score.math.avg()).fetch();
    }
}

주석을 기준으로 각각 3개의 클래스에 작성되어 있는 코드임

  • service
@Service
@RequiredArgsConstructor
public class ScoreService {

    @NonNull
    private final ScoreRepository scoreRepository;

    public List<Score> findAll(){
        return scoreRepository.findAll();
    }

    public List<Tuple> findScoreAvgList(){
        return scoreRepository.findScoreAvgList();
    }
}
  • index.html
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <table>
        <thead>
        <tr>
            <th>Name</th>
            <th>Korea</th>
            <th>English</th>
            <th>Math</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each = "score : ${scoreList}">
            <td>
                <input type="checkbox" name="students" th:value = "${student.name}" th:text="${student.name}" checked>
            </td>th:text="|${score.korea} 점|"></td>
            <td th:text="|${score.english} 점|"></td>
            <td th:text="|${score.math} 점|"></td>
        </tr>
        </tbody>
    </table>
</div>
</body>
</html>

요로코롬 보입니더 👇


📌 chart.js 사용해서 데이터 보여주기

🧷 chart.js 추가하기

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/Chart.min.js"></script>

chart.jsjquery 가 안 불러와진다 왜이런담..
이전에도 이런 적이 많아서 잊지 않기 위해 기록해 두었다.

👉작성 예정👈

🧷 그래프 그리기 📊

<div>
    <canvas id = "scoreChar"></canvas>
</div>
<script type = "text/javascript" th:inline="javascript">
    /*<![CDATA[*/
    $(document).ready(function () {
        const scoreAvgList = /*[[ ${scoreList.toArray()} ]]*/;

        console.log(scoreAvgList);
        const ctx = document.getElementById('scoreChart').getContext('2d');
        const chart = new Chart(ctx, {
            type: 'bar', //chart 타입
            data: {
                labels: ['국어', '영어', '수학'],
                datasets:[{
                    fill: false,
                    data: scoreAvgList,
                    backgroundColor: [
                        'rgba(255, 206, 86, 0.2)',
                        'rgba(75, 192, 192, 0.2)',
                        'rgba(153, 102, 255, 0.2)'
                    ],
                    borderColor: [
                        'rgba(255, 206, 86, 1)',
                        'rgba(75, 192, 192, 1)',
                        'rgba(153, 102, 255, 1)'
                    ],
                    borderWidth: 1,
                    barPercentage: 0.5
                }]
            },
            options:{
                scales: {
                    y: {
                        grid: {
                            drawOnChartArea: true,  //선 지우기
                            drawTicks: false,   //축의 숫자 옆 눈금 지우기
                            drawBorder: true,
                            borderDash: [3, 3]	//y축 선 실선으로 길이 3,간격 3으로
                        },
                        ticks: {
                            padding: 10,
                            beginAtZero: true
                        }
                    },
                    x: {
                        grid: {
                            display: false,
                            drawBorder: false,
                            drawTicks: false
                        },
                        ticks: {
                            padding: 10
                        }
                    }
                }
            }
        });
    });
    /*]]>*/
</script>

요로코롬 보입니더 👇


현재 그래프는 모든 학생들의 국어, 영어, 수학 평균 점수를 보여주고 있다.

chart.js의 data에 리스트([x: 34, y: 23] 형식도 됨), 배열 타입이 들어가는 것을 알고 있었지만, Tuple의 모양도 [34, 34, 34] 와 같아서 그대로 넣어주면 그래프가 그려질 줄 알았다. 생각과는 다르게 오류가 내게 인사해주었고 🤗, 모양만 같은 건 아무 필요 없다는 것을 다시 한번 알게 되었다.

결국 타입을 맞춰주기 위해 toArray() 를 사용하여 배열 타입으로 만들어 주었다.

👉chart.js 데이터 구조: https://www.chartjs.org/docs/latest/general/data-structures.html👈



📌 선택된 데이터만 그래프에 나타내기

view에서 어떤 데이터가 선택되었는지 알기 위해 form을 사용하여 선택된 데이터를 POST 해줄 것이다.

선택된 데이터의 이름을 사용하여 where 절에 equal 조건을 주고, 해당 이름을 가진 튜플만 가져올 수 있도록 하기 위해 List<String> students 인자를 추가해주었다.

  • controller
 @PostMapping(value = "/", produces = "application/json;charset=utf-8")
    @ResponseBody
    public ResponseEntity formulationResult(@RequestParam("students") List<String> students) {
        Tuple scoreList = scoreService.findScoreAvgList(students); 
        return ResponseEntity.ok(scoreList);
    }

파라미터가 추가됨에 따라 repository, service 등을 수정해주어야 한다.

  • index.html
<div></div>로 감싸줬던 <table><form></form>으로 감싸주었다.

data update

 function updateChartData(data){
            if(data){
                chart.data.datasets.data[0] = data.korea;
                chart.data.datasets.data[1] = data.english;
                chart.data.datasets.data[2] = data.math;
            }
            else{
                chart.data.datasets.data = [];
            }
            chart.update();
        }

data post

function calculateAvgForm(){
            const formData = $('#cal-avg-form').serialize();
            $.ajax({
                url: /* [[ @{/} ]]*/'',
                method: 'post',
                dataType: 'json',
                data: formData
            }).done(function (data){

                updateChartData(data);
            }).fail(function (){
                $('#korea').text('-');
                $('#english').text('-');
                $('#math').text('-');

                updateChartData(data);
            });
        }

check change event

$('input[name="students"]').on('change', function() {
            calculateAvgForm();
        });

🧷 결과물 확인해 보기 👀

디버깅으로 확인해보니 선택된 데이터만을 잘 가져온다. 여기서 form은 check 되어있지 않은 데이터를 POST할 때 기본적으로 포함하지 않는다는 것을 알게 되었다.
사실 이게 이 연습용 프로젝트를 하게 된 가장 큰 이유였다.

처음에 관련된 코드를 보았을 때, 이상했다. 체크 여부를 확인하고 데이터를 제외하는 코드를 찾을 수 없었기 때문이다.
사수 분과 인터넷 서칭을 통해 form의 기본적인 기능이었다는 것을 알게 되었다. 코드를 직접 작성한 것이 아니었기에 솔직히 긴가민가 했다. 나 몰래 어딘가에 코드를 숨겨놓았을..

그래서 직접 코드 일부를 참고해가며 작성했다. 정말로 그냥 되네..

form은 input타입의 checkbox가 체크되어 있는지의 여부를 확인하는 코드를 작성하지 않았음에도 알아서 필터링하여 데이터를 POST해준다.

이와 관련한 내용은 여기
👉HTML4 권장사항 https://www.w3.org/TR/2012/WD-html5-20120329/form-submission.html#constructing-form-data-set👈



📌 그런데.. 그래프가 휑~ 하네? 🤔

참고한 코드에서는 DTO를 통해 데이터를 가공? 하고 있었다. 그런데 나는 귀찮다는 이유로 그냥 Tuple로 받았고, 이를 그래프로 그리기 위해 toArray()를 사용하여 배열로 타입을 바꿔 넣어주었다.

그리고 이것을 잠깐 동안 잊고 있었다. 바보가 아닌가..

먼저 controller에서 Tuple 타입을 배열로 바꾸어 주었다.

return ResponseEntity.ok(scoreList.toArray());
  • index.html - data update

DTO를 사용했다면

 chart.data.datasets.data[0] = data.korea;
 chart.data.datasets.data[1] = data.english;
 chart.data.datasets.data[2] = data.math;

이와 같이 쓰는 것이 맞으나 나는 배열로 타입을 바꿔주었기 때문에

 chart.data.datasets[0].data[0] = data[0];
 chart.data.datasets[0].data[1] = data[1];
 chart.data.datasets[0].data[2] = data[2];

위와 같이 작성해주어야 했다.


끝 🎃

gif 파일로 바꿀 예정

chart 관련 코드 전에 calculateAvgForm(); 로 메소드를 한번 호출해주어야 한다.
이걸 또 빼먹어서 그래프가 그려지지 않았다. (메소드 선언만 하고 호출은 안한거..ㅎ ㅏ)



오류 발견 😰

gif를 만들기 위해 차트를 확인하던 중 모든 데이터에 대한 체크 박스를 false로 만들었을 때 마지막에 체크 해제한 데이터의 값이 그래프에 남아있는 것을 확인하였다. 그리고 리스트의 첫번째 값인 라이언의 값이 '-' 로 바뀌게 된다.

데이터 전송에 실패했을 때 '-'로 바꿔주는 코드가 있는데 이게 여기서 작동하는 것 같다.

코드를 한 번 다시 확인하고 수정해야겠다.

2021-10-19) 코드 수정 후 해결

  • 평균 값을 나타내는 text를 추가하고 이를 데이터가 없을 때 '-' 로 나타나게 함
  • updateChartDate() 에서 데이터가 없을 때의 예외 사항을 다루고 있기 때문에 파라미터를 넘기지 않고 호출하게 함 (파라미터를 넘기게 되면 오류가 발생하여 메소드가 호출되지 않아 이전 데이터가 차트에 남아있던 것)
.fail(function () {
        $('#korea_avg').text('-');
        $('#english_avg').text('-');
        $('#math_avg').text('-');

        updateChartData();
      });
  • 차트의 데이터를 업데이트할 때 datasets의 참조가 잘못되어 있어서 수정함
else {
        chart.data.datasets[0].data = [];
        }
//원래 chart.data.datasets.data = []; 였음

0개의 댓글