DOIT BETTER

Chart.js 사용법 + DB 연동 본문

Dev

Chart.js 사용법 + DB 연동

sangkins 2024. 7. 14. 23:32

 

DB에서 값을 가져와서 시각적으로 보여주어야 하는 일이 생겼다.

다양한 차트를 지원하면서 MIT 라이센스 오픈 소스 라이브러리를 찾다보니 chart.js를 찾았다.

Chart.js는 다양한 차트를 지원하는 MIT 라이센스 오픈 소스 라이브러리로 다양한 차트를 지원한다.

  • 바 차트 (Bar chart)
  • 라인 차트 (Line chart)
  • 도넛/파이 차트 (Doughnut/Pie chart)
  • 레이더 차트 (Radar chart)
  • 극좌표 차트 (Polar area chart)
  • 버블 차트 (Bubble chart)
  • 스캐터 차트 (Scatter chart)

사용할 차트는 도넛/파이 차트, 라인 차트, 바 차트를 사용하려고 한다.

 

Spring, MyBatis, JSP를 사용하는 MVC 프로젝트에서 DB에서 가져온 값을 Chart.js로 구현하는 방법

IntelliJ IDEA 2024.1, Oracle DB ,Amazon corretto 8, MyBatis, Spring4, jsp

 

Chart.js 초기 세팅

  1. npm
  2. CDN
  3. jsDeliver
  4. Github

다양한 방식이 있는데 Github에서 다운 받을 경우 빌드해야 하기 때문에  jsDeliver 에서 다운받았다.

 

이 중 jsDelivr를 통해 다운로드 받는 방법을 선택했습니다. jsDelivr 웹사이트에서 다운로드 버튼을 클릭하여 압축 파일을 받은 후, 압축을 해제하고 dist 폴더 안의 chart.umd.js 파일을 프로젝트의 resources/js 폴더에 추가했습니다.

 

chart.umd.js 를 js 폴더 아래에 두었다.

 

 

Spring, MyBatis, jsp를 사용하는 MVC 프로젝트에서 DB에서 가져온 값을 Chart.js 로 구현하는 것이 간단해 보였지만 공식메뉴얼에도 연동하는 방법에 대해서는 정확히 나와 있지 않아서 구현해 보았다.

 

<canvas id="doughnut-chart2"></canvas>

 

주의할 점 : 스크립트가 canvas 선언 밑으로 와야 한다. 

 

chart.js Failed to create chart: can't acquire context from the given item

I never got into node so I am pretty sure I am doing something massively wrong here since I cannot find any info at all by googling. I have a django site and I wanted a JS charting library, I chose

stackoverflow.com

Make sure to declare the canvas tag in html before the script that creates the Chart.js object. Otherwise, the script executes and tries to find a reference to a canvas that doesn't exist. In the script, any of the following formats may be used to get a reference to the canvas, which is then passed to the Chart.js constructor.

 

DB 연동 전 차트가 제대로 그려지는지 확인하기 위해 샘플 데이터를 넣은 도넛 차트를 생성한다.

new Chart(document.getElementById("doughnut-chart"), {
    type: 'doughnut',
    data: {
      labels: ["SEND SUCCESS", "SEND FAIL", "TOTAL"],
      datasets: [
        {
          label: "CNT",
          backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f"],
          data: [5000,2340,7340]
        }
      ]
    },
    options: {
      title: {
        display: true,
        text: 'COUNT'
      }
    }
});

 

차트가 화면에 정상적으로 출력되는 것을 확인한다.

 

 

1. DB Table 생성 (Oracle)

create table status (TYPE VARCHAR2(50), CNT INT);

 

2. MyBatis Mapper 작성

<select id="selectSendCount" parameterType="java.util.HashMap" resultType="com.test.vo.GraphVO">
	select type, CNT  
    	from STATUS
</select>

 

3. 인터페이스 작성, 서비스 클래스 작성

public interface StatusDao {
    List<StatusDto> getStatusData();
}
public interface StatusDao {
    List<StatusDto> getStatusData();
}

@Service
public class StatusService {
    @Autowired
    private StatusDao statusDao;

    public List<StatusDto> getStatusData() {
        return statusDao.getStatusData();
    }
}

 

 

4. Controller 작성

@Controller
@RequestMapping("")
public class ChartController {
    @Autowired
    private StatusService statusService;

    @GetMapping("/chart")
    public String showChart(Model model) {
        List<StatusDto> statusData = statusService.getStatusData();
        model.addAttribute("statusData", statusData);
        return "chart";
    }
}

 

 

5. Jsp 페이지에서 Chart.js 사용

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<canvas id="doughnut-chart"></canvas>

<script>
    var statusData = [
        <c:forEach items="${statusData}" var="status" varStatus="loop">
            {
                type: "${status.type}",
                cnt: ${status.cnt}
            }<c:if test="${!loop.last}">,</c:if>
        </c:forEach>
    ];

    new Chart(document.getElementById("doughnut-chart"), {
        type: 'doughnut',
        data: {
            labels: statusData.map(item => item.type),
            datasets: [{
                label: "CNT",
                backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f"],
                data: statusData.map(item => item.cnt)
            }]
        },
        options: {
            title: {
                display: true,
                text: 'COUNT'
            }
        }
    });
</script>

 

 

이렇게 까지 하면 DB 에서 데이터를 가져와 차트에 표현하는 것을 볼 수 있었다.

 

DB에 데이터가 변경될 경우에만 가지고 오는 WebSocket 방식도 있지만 간단하게 ajax 요청으로 3초마다 요청하는 방법을 선택했다.

더보기

- WebSocket: 양방향 실시간 통신이 가능하며, 서버에서 데이터가 변경될 때만 클라이언트에 알릴 수 있다.

- Server-Sent Events (SSE): 서버에서 클라이언트로의 단방향 실시간 통신이 가능하다.

 

이 방법은 서버의 부하를 줄 수 있지만 다른 방법이 생각나지 않아 이렇게 사용했다.

 

차트를 업데이트하는 함수, 초기 차트 이렇게 구분지어서 5. Jsp 페이지에서 Chart.js 사용 을 Refactoring했다.

 

6. 변경된 Chart.js 사용

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<canvas id="doughnut-chart"></canvas>

<script>
    var chart;
    var chartDataUrl = '<c:url value="/chart-data"/>';

    function createChart(data) {
        chart = new Chart(document.getElementById("doughnut-chart"), {
            type: 'doughnut',
            data: {
                labels: data.map(item => item.type),
                datasets: [{
                    label: "CNT",
                    backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f"],
                    data: data.map(item => item.cnt)
                }]
            },
            options: {
                responsive: true,
                title: {
                    display: true,
                    text: 'COUNT'
                },
                legend: {
                    position: 'bottom'
                },
                animation: {
                    animateScale: true,
                    animateRotate: true
                }
            }
        });
    }

    function updateChart() {
        $.ajax({
            url: chartDataUrl,
            method: 'GET',
            success: function(data) {
                chart.data.labels = data.map(item => item.type);
                chart.data.datasets[0].data = data.map(item => item.cnt);
                chart.update();
            },
            error: function(error) {
                console.error('Error fetching chart data:', error);
                alert('차트 데이터를 불러오는 데 문제가 발생했습니다. 잠시 후 다시 시도해주세요.');
            }
        });
    }

    // 초기 차트 생성
    $.ajax({
        url: chartDataUrl,
        method: 'GET',
        success: function(data) {
            createChart(data);
            // 3초마다 차트 업데이트
            setInterval(updateChart, 3000);
        },
        error: function(error) {
            console.error('Error :', error);
        }
    });
</script>

 

setInterval은 서버의 부하를 고려해 적절히 설정해주어도 된다.

 

 

Controller 에서 Json 형식으로 데이터를 주고 받아도 된다. 그럴때는 VO 객체를 생성해서 JsonArray에 매핑해주면 된다.

커스텀해서 생성했지만 간단한 방법도 있다 (주석 참고).

더보기
 private static String convertGraphVOListToJson(ArrayList<GraphVO> list) {
        JSONArray jsonArray = new JSONArray();
        if (list != null) {
            Iterator<GraphVO> it = list.iterator();
            while (it.hasNext()) {
                GraphVO gVo = it.next();
                JSONObject obj = new JSONObject();
                obj.put("TYPE", gVo.getType());
                obj.put("COUNT", gVo.getCnt());
                jsonArray.add(obj);
            }
        }
        return jsonArray.toJSONString();
    }
    
    
    ************************** Spring **************************
    
    //Spring이 객체를 Json으로 변환
    @RestController
    public class ChartController {
        @GetMapping("/chart-data")
        public List<GraphVO> getChartData() {
            return statusService.getStatusData();
        }
    }
    
    ************************** Spring **************************

 

 

json으로 받는 경우 datasets -> data : cntList로 넣어주면 됩니다.

$.ajax({
	url: "../chart-data",
    type: "GET",
    dataType: "json",
    success: function(data) {
    let dataArray = data.statusData;
    for (let i = 1; i < dataArray.length; i++) {
    	cntList.push(dataArray[i].CNT);
    }
    ...............

 

 

window.onload 이후에 사용

//차트 Init 함수 (예시)
window.onload = initializeChart;

 

 

 

차트가 정상적으로 출력되는 것을 확인할 수 있다.