Spring

[Spring/Ajax] Chart.js로 차트 만들기

코북 2021. 10. 30. 19:36

 안녕하세요 코북입니다. 이번 프로젝트에서 측정한 거북목 DB를 사용자에게 시각화하여 보여주기 위해 차트를 만들게 됐습니다. 자료를 찾아보니까 Chart.js라는 사이트에서 Javascript를 사용하여 간편하게 차트를 구현할 수 있게 오픈 소스를 제공하고 있었습니다. 세상에는 정말 많은 천재들이 있구나 하는 생각이 들었습니다. 사이트 링크와 작업 진행 순서입니다.

https://www.chartjs.org/

 

Chart.js | Open source HTML5 Charts for your website

New in 2.0 New chart axis types Plot complex, sparse datasets on date time, logarithmic or even entirely custom scales with ease.

www.chartjs.org

 

  1. Chart.js
  2. Mysql DB
  3. VO
  4. MyBatis XML Mapper
  5. Mapper Interface
  6. Controller
  7. JavaScript/ajax

 

1. Chart.js로 차트 만들기

 먼저 웹 페이지에서 제대로 보여지는지 확인하기 위해 chart.js를 통해 텅 빈 그래프를 만들었습니다. 잘 구현됩니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<canvas id="line-chart" width="300" height="250"></canvas>
<script>
  new Chart(document.getElementById("line-chart"), {
    type: 'line',
    data: {
      labels: [1500,1600,1700,1750,1800,1850,1900,1950,1999,2050],
      datasets: [{ 
          data: [86,114,106,106,107,111,133,221,783,2478],
          label: "Africa",
          borderColor: "#3e95cd",
          fill: false
        }, { 
          data: [282,350,411,502,635,809,947,1402,3700,5267],
          label: "Asia",
          borderColor: "#8e5ea2",
          fill: false
        }, { 
          data: [168,170,178,190,203,276,408,547,675,734],
          label: "Europe",
          borderColor: "#3cba9f",
          fill: false
        }, { 
          data: [40,20,10,16,24,38,74,167,508,784],
          label: "Latin America",
          borderColor: "#e8c3b9",
          fill: false
        }, { 
          data: [6,3,2,2,7,26,82,172,312,433],
          label: "North America",
          borderColor: "#c45850",
          fill: false
        }
      ]
    },
    options: {
      title: {
        display: true,
        text: 'World population per region (in millions)'
      }
    }
  });
</script>

2. Mysql DB

 이제 차트에 넣을 DB를 구성해보겠습니다. Posture 테이블은 pos_seq를 PK로 설정하고 회원 이이디를 FK로 설정했습니다. 회원 테이블은 회원이 PK입니다. 두 테이블은 1:N 관계입니다. 데이터 베이스를 만들었으면 테스트를 위해 DB에 미리 값을 넣어줍니다.

-- 자세 테이블
CREATE TABLE POSTURE
(
    `POS_SEQ`    INT UNSIGNED    NOT NULL    AUTO_INCREMENT COMMENT '자세 순번', 
    `POS_TYPE`   VARCHAR(30)     NOT NULL    COMMENT '자세 종류', 
    `POS_TIME`   DATE            NOT NULL    COMMENT '발생일자', 
    `POS_COUNT`  INT UNSIGNED    NOT NULL    COMMENT '발생횟수', 
    `MB_ID`      VARCHAR(30)     NOT NULL    COMMENT '회원아이디', 
     PRIMARY KEY (POS_SEQ)
);

ALTER TABLE POSTURE COMMENT '자세정보';

ALTER TABLE POSTURE
    ADD CONSTRAINT FK_POSTURE_MB_ID_MEMERS_MB_ID FOREIGN KEY (MB_ID)
        REFERENCES MEMERS (MB_ID) ON DELETE RESTRICT ON UPDATE RESTRICT;

-- DB에 넣을 값
insert into POSTURE(POS_TYPE,POS_TIME,POS_COUNT,MB_ID) values('거북목','2021-10-27','48','adkim');
insert into POSTURE(POS_TYPE,POS_TIME,POS_COUNT,MB_ID) values('눈깜빡임','2021-10-27','2048','adkim');

insert into POSTURE(POS_TYPE,POS_TIME,POS_COUNT,MB_ID) values('거북목','2021-10-26','13','adkim');
insert into POSTURE(POS_TYPE,POS_TIME,POS_COUNT,MB_ID) values('눈깜빡임','2021-10-26','3060','adkim');

 3. VO 생성

 만든 테이블 컬럼에 맞는 VO를 생성해줍니다. Lombok API를 사용하면 getter, setter, 생성자 없이 VO를 구성할 수 있어 좋은 것 같습니다.

package city.turtle.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class GraphVO {
	
	private int pos_seq;
	private String pos_type;
	private String pos_time;
	private int pos_count;
	private String mb_id;
	

}

4. MyBatis XML Mapper

 테이블과 VO를 생성했으니 이제 차트에 넣을 값을 select 해줄 쿼리를 작성하면 됩니다. 사용자가 자신의 ID에 해당하는 데이터를 볼 수 있어야 하고 차트의 종류는 두 가지로 만들 계획입니다. 시간은 현재부터 7일 이전까지 보여줄 수 있도록 설정했습니다. BETWEEN A AND B와 DATE_SUB 문법을 사용했습니다. 이 xml파일은 mapper에서 설정한 namespace를 통해 Interface와 연결됩니다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="city.turtle.service.GraphService">
 
    <select id="countTurtle" resultType="city.turtle.vo.GraphVO">
        select * from POSTURE 
        where mb_id=#{mb_id} and pos_type=#{pos_type} 
        and pos_time between DATE_SUB(NOW(), INTERVAL 7 DAY) and NOW() 
        order by pos_time;        
    </select>

</mapper>

5. Mapper Interface

xml파일의 명령을 처리해 줄 Interface를 만들어줍니다. GraphVO 클래스의 pos_type과 mb_id 속성을 사용해 쿼리를 실행시킬 것이기 때문에 매개변수는 GraphVO 클래스로 설정했습니다. 결괏값은 7개의 값을 모두 담아줘야 하기 때문에 List<GraphVO>로 리턴 값을 설정해줬습니다.

package city.turtle.service;

import java.util.List;

import city.turtle.vo.GraphVO;

public interface GraphService {
	
	// 거북목 카운트
	public List<GraphVO> countTurtle(GraphVO vo) throws Exception;
	
}

■ root-context.xml

 새로 만든 패키지들을 scan할 수 있도록 root-context.xml에 mybatis-spring:scan코드를 입력해줍니다.

	<mybatis-spring:scan base-package="city.turtle.vo"/>
	<mybatis-spring:scan base-package="city.turtle.service"/>

6. Controller

 ajax방식으로 countTurtle.do가 요청되면 컨트롤러가 작동할 수 있도록 @RequestMapping 설정해주시고 json 데이터를 처리할 것이기 때문에 @ResponseBody를 사용합니다. 위에서 말한 것처럼 받아오는 값은 List<GraphVO>이고 사용자에게 받은 GraphVO vo값을 넘겨줘서 countTurtle 메소드를 실행시킵니다. 객체 바인딩을 해야 할지 값 자체를 넘겨줄지 고민 중이어서 일단 두 방식 모두 작성했습니다.

package city.turtle.web;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import city.turtle.service.GraphService;
import city.turtle.vo.GraphVO;

@Controller
public class GraphController {
	
	@Autowired
	private GraphService mapper;
	
	@RequestMapping("/countTurtle.do")
	public @ResponseBody List<GraphVO> countTurtle(Model model, GraphVO vo) throws Exception {
		List<GraphVO> turtle = mapper.countTurtle(vo);
		model.addAttribute("turtle", turtle);
		return turtle;
	}

}

7. JavaScript / Ajax

 이제 비동기 통신을 구현하기 위해 javascript 코드를 작성합니다. $(document).ready()는 문서가 준비됐을 때 매개변수로 넣은 Callback함수를 실행하라는 jQuery메소드입니다. 저는 차트를 만들어주는 메소드를 getGraph() 라는 이름으로  만들었습니다. getGraph() 메소드는 ajax를 실행시켜줍니다. url은 컨트롤러에서 설정한 countTurtle.do로 하여 컨트롤러에 요청을 보냅니다. 보낼 데이터는 xml에서 사용할 #{mb_id} 와 #{pos_type}이고 json방식으로 보내줍니다. 요청이 성공하면 콜백 함수는 data에 결괏값을 보내줍니다. 차트에서 보여줄 값을 리스트 형식으로 만들어줘야 하기 때문에 반복문을 통해서 넘어온 값을 push()를 사용해 미리 만들어둔 timeList, posList 리스트에 담아줍니다. 바로 이어서 chart.js에서 제공하는 코드를 통해 그래프를 그려줍니다. data.data는 차트로 보여줄 값을 의미하고, data.labels는 각 값들에 붙는 이름을 의미합니다. posList는 data, timeList는 labels로 설정했습니다. 요청에 실패할 경우 '실패'라는 메시지를 알림 창으로 띄우도록 했습니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript">

      $(document).ready(function(){ 
    		getGraph();
    	});
      
      function getGraph(){
       	  let timeList = [];
    	  let posList = [];
    	  
    	  $.ajax({
    		  url:"${cpath}/countTurtle.do",
    		  type:"get",
    		  data:{mb_id:"${signIn.mb_id}", pos_type:"거북목"},
    		  dataType:"json",
    		  success:function(data){
    			  // console.log(data[0].pos_count);
    			  // 그래프로 나타낼 자료 리스트에 담기
    			  for (let i = 0; i<data.length;i++){    				  
						timeList.push(data[i].pos_time);    				  
						posList.push(data[i].pos_count);    				  
    			  }
    			  // console.log(timeList);
    			  // console.log(posList);  	
				  // 그래프
    			  new Chart(document.getElementById("line-chart"), {
    		    	  type: 'line',
    		    	  data: {
    		    	    labels: timeList, // X축 
    		    	    datasets: [{ 
    		    	        data: posList, // 값
    		    	        label: "거북목",
    		    	        borderColor: "#3e95cd",
    		    	        fill: false
    		    	      }
    		    	    ]
    		    	  },
    		    	  options: {
    		    	    title: {
    		    	      display: true,
    		    	      text: '주간 거북목'
    		    	    }
    		    	  }
    		    	}); //그래프
    		  },
    		  error:function(){
    			  alert("실패");
    		  }  
	     		  
    	  }) // ajax	  
      } // getGraph
      
</script>

 

 

구현 화면

 구현 화면입니다. 최근 7일 간의 데이터가 들어간 그래프가 그려졌습니다. 파이 차트는 그냥 틀만 있는 상태입니다.

사용 기술

Spring MVC, JavaScript, JQuery, Ajax, EL

 

배운 점

 Spring연결 부분에서는 초기에 Mapper와 XML파일의 이름을 다르게 만들어 오류가 발생했으나, 팀원과 함께 디버깅하면서 오류를 찾아낼 수 있었습니다. mysql 쿼리를 작성 부분에서는 DATE_SUB( , INTERVAL ) 메소드와 NOW()를 사용해 현재에서 시간을 더하거나 뺄 수 있었습니다. NOW()가 단순하게 현재 시간만을 나타내 주는 것이 아니라 응용해서 사용할 수 있다는 것을 배웠습니다. 리스트에 for문으로 만든 값들을 넣어서 차트를 만드는 식에 응용하면서 지역변수들이 메모리에 저장되는 범위에 대해 생각할 수 있는 시간이었습니다. success 콜백 함수 안에서 for문을 돌릴 수 있다는 것도 다시 한번 인지할 수 있었습니다.

 

 

본 글은 아래 링크의 내용을 참고하여 학습한 내용을 나름대로 정리한 글임을 밝힙니다.

Chart.js

https://www.chartjs.org/

https://nomalcy.tistory.com/23

https://stackoverflow.com/questions/19894952/draw-a-chart-js-with-ajax-data-and-responsive-a-few-problems-and-questionshttps://sun-p.tistory.com/116

mysql 시간 더하기, 빼기

https://extbrain.tistory.com/58

Ajax에서 for문 사용

https://integer-ji.tistory.com/263