본문 바로가기
프로그래밍/javascript

[프로그래밍] chart.js를 이용한 대시보드 만들기

by 개발 까마귀 2021. 1. 30.
반응형

chart.js를 이용한 대시보드 만들기

안녕하세요. 개발 까마귀입니다. 여러분 대시보드는 어떨 때 많이 사용할까요? 대시보드는 특정한 데이터를 편리하고 한눈에 볼 수 있게 많이들 사용합니다. 예를 들어서 학교에 여학생 남학생 비율을 원형 그래프로 나타내고 코로나 확진자 수를 막대 그래프로 나타냅니다. 그래서 chart.js로 저희가 한번 차트를 그려보도록 하죠

저희가 차트를 그리기 위해서는 chart.js라는 라이브러리를 사용할겁니다. 사실 차트 그리는 거는 D3.js 유명하지만 chart.js도 간단하면서도 쉽게 차트를 만들 수 있습니다.

 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
    <title>Document</title>

    <style>
        * {
            margin: 0;
            padding: 0;
        }

    </style>
</head>

<body>
    <div class="content">
        <div class="row" id="chratBox" style="width:100%;"></div>
    </div>
</body>
</html>

 

처음에는 html파일을 생성하고  <script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script> 이 태그를 head 태그 안에 넣어주시고 생산성을 높이기 위해 jquery까지 넣으신 후 body태그에 div 태그를 생성하시면 됩니다. 저 div 태그에 chartBox에 이젠 저희 chart를 넣을 겁니다.

 

<script>
    function Chart() { }

    Chart.prototype.color = {
        chartBackgroundColor: [
            'rgba(75, 192, 192, 0.5)',
            'rgba(255, 99, 132, 0.5)',
            'rgba(3, 47, 249, 0.4)',
            'rgba(153, 102, 255, 0.5)',
            'rgba(106, 166, 45, 0.5)',
            'rgba(255, 159, 64, 0.5)',
            'rgba(52, 213, 94, 0.5)',
            'rgba(213, 52, 162, 0.5)',
        ],
        chartBorderColor: [
            'rgba(255, 99, 132, 1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)',
            'rgba(249, 20, 3, 1)',
            'rgba(255, 252, 24, 1)',
            'rgba(52, 213, 94, 1)',
            'rgba(213, 52, 162, 1)',
        ]
    }
</script>

script태그를 선언한 후 이렇게 차트의 색깔을 지정합시다. 우선 color 객체를 만들고 거기에 차트의 background-color와 border-color를 배열을 만듭시다.  이렇게 빼는 이유는 마지막에 알려드리겠습니다.

 

    Dashboard.prototype.options = {
        asianPopulation: {
            type: 'bar',
            labels: [],
            elements: {
                title: "세계 인구 현황",
                width: 4,
                desc: "세계인구 현황(남자/여자) 구별 안함",
                rows: 1
            },
            data: [{
                label: '세계인구',
                data: [],
            }]
        }
    }

차트를 만들 객체를 만듭시다.

 

type => 차트를 만들 차트 type입니다. 한마디로 막대, 원형 그래프 등 설정 해주는겁니다.

labels = > 아래쪽에 나오는 labels입니다.

elemetns => 이거는 제가 따로 만든 객체입니다. chart html 만들 때 편리하게 하려고 만들었습니다.

data label => 차트 title이라고 보며 됩니다.

data data => 차트를 나타내는 데이터를 담을 곳입니다.

 

이것 또한 정적 데이터입니다.

 

Dashboard.prototype.drawChartOptions = {
        scales: {
            yAxes: [{
                ticks: {
                    beginAtZero: true
                }
            }],
            xAxes: [{
                stacked: true
            }],
        }
    }

차트를 담을 부가적인 옵션들입니다. 공식 홈페이지에 나오는 옵션들이어서 그냥 붙여 넣었습니다. 필요하긴 합니다.

이렇게 정적인 객체 데이터를 만들었으니 동적인 기능을 만드는 코드를 짜 봅시다.

 

    Dashboard.prototype.init = function () {
        this.getData();
    }

    Dashboard.prototype.getData = function () {
        const _this = this;
        this.getDataCallback = function (data) {
            if (data.length === 0) {
                return console.error("데이터가 존재하지 않습니다.", data)
            } else {
                _this.chartAppend(data);
            }
        }

        setTimeout(function () {
            _this.getDataCallback({
                asianPopulation: [{
                   data: [50, 500, 300, 200],
                   labels: ['한국', '중국', '러시아', '일본']
                }]
            });
        }, 1000);
    }

init이나 start로 많이들 사용합니다. 그냥 시작한다는 의미입니다.

getData 함수는 data를 받아서 체크를 한 다음 넘겨주는 함수입니다. 원레는 ajax로 호출을 한 다음 백엔드에서 데이터 가공을 해서 프론트에서는 받는 거를 간단한 게 표현했습니다. ajax는 비동기이니 setTimeout으로 해서 함수로 데이터를 보냅니다. 실무에서 이렇게 많이 사용합니다.

 

    Dashboard.prototype.chartAppend = function (data) {
        const elements = [];

        for (let [key, value] of Object.entries(this.options)) {
            let chartHtml = `
            <div class="col-${value.elements.width}" >
                <div class="card" style="width: 100%;">
                    <div class="card-header" style="background-color:white"> 
                        <span class="card-title" style="
                        text-align:center;
                        margin-top: 10px; 
                        font-size: 2em; 
                        color:#252422; 
                        font-weight:500;
                        ">
                        ${value.elements.title}
                        </span>
                    </div>
                    <div class="card-body"> 
                        <canvas id=${key}Chart></canvas>
                    </div>
                    <div class="card-footer"
                        style="
                        background-color: rgba(0,0,0,.03);
                        border-top: 1px solid rgba(0,0,0,.125);
                        ">
                        <small class="text-muted"> ${value.elements.desc}</small>
                    </div>
                </div>
             </div>`;
            elements.push(chartHtml);
        }
        $("#chratBox").append(elements.join(''));
        this.dataInsert(data);
    }

차트를 그리기 전에 담을 HTML Elements를 만드는 코드입니다. append를 for문에 놓는 거는 리소스 낭비이니 배열에서 담은 후 그걸 join에서 append를 한 겁니다. 여기서 elements를 만든 이유를 알겠죠?

 

    Dashboard.prototype.dataInsert = function (data) {
        for (let [key, value] of Object.entries(this.options)) {
            for (let i = 0; i < data[key].length; i++) {
                value.labels = data[key][i].labels
                value.data[0].data = data[key][i].data
            }
            this.drawChart(key, value);
        }
    }

여기는 2중 for문으로 차트에 데이터를 담는 코드입니다. 이렇게 data[key]를 하면 아까 받은 data에 ["

asianPopulation"]를 찾아서 데이터를 넣는 코드입니다. 이렇게 하면 객체 데이터를 몇 개를 넣어도 데이터를 알아서 넣습니다. 

 

    Dashboard.prototype.drawChart = function (key, value) { //공통 option 및 차트 그리기
        const ctx = document.getElementById(key + "Chart");
        value.data[0].backgroundColor = this.color.chartBorderColor;
        value.data[0].backgroundColor = this.color.chartBackgroundColor;
        value.data[0].borderWidth = 1;

        new Chart(ctx, {
            type: value.type,
            data: {
                labels: value.labels,
                datasets: value.data
            },
            options: this.drawChartOptions
        });
    }

    const dashboard = new Dashboard();
    dashboard.init();

key, value는 key는 차트를 그릴 canvas id입니다. value는 차트를 그릴 때 필요한 저희가 선언한 데이터입니다.

위에 데이터 넣는 함수 있었죠? 차트를 그릴 데이터만큼 반복문을 돌려서 그 반복한 횟수만큼 차트를 그려야 하기 때문에 반복문 안에 drawChart를 선언한 겁니다. drawChart 함수는 차트를 담을 canvas를 선언 후 저희가 만들었던 color 데이터를 넣은 후 new Chart 여기서 선언한 데이터를 넣어주면 차트를 그릴 준비는 다 끝났습니다. 

 

그리고 생산자를 선언 후 init을 하면 차트가 그려집니다.

 

 

 

짜잔~~ 대단하죠?? 저희가 만든 겁니다. 별로 손대지도 않았는데 정말 이쁘게 나왔습니다. 근데 차트는 하나만 하지는 않죠? 저희가 정적인 데이터를 따로 뺀 이유는 중복이 돼서 뺸것도있지만 재활용성을 위한 겁니다. 정적인 거는 어는 상황에서든 똑같은 역할만 하니 계속 코드의 흐름을 따라올 필요는 없기 때문입니다.

한번 차트를 3개 정도 만들어보죠

 

    Dashboard.prototype.color = {
        chartBackgroundColor: [
            'rgba(75, 192, 192, 0.5)',
            'rgba(255, 99, 132, 0.5)',
            'rgba(3, 47, 249, 0.4)',
            'rgba(153, 102, 255, 0.5)',
            'rgba(106, 166, 45, 0.5)',
            'rgba(255, 159, 64, 0.5)',
            'rgba(52, 213, 94, 0.5)',
            'rgba(213, 52, 162, 0.5)',
        ],
        chartBorderColor: [
            'rgba(255, 99, 132, 1)',
            'rgba(54, 162, 235, 1)',
            'rgba(255, 206, 86, 1)',
            'rgba(75, 192, 192, 1)',
            'rgba(153, 102, 255, 1)',
            'rgba(255, 159, 64, 1)',
            'rgba(249, 20, 3, 1)',
            'rgba(255, 252, 24, 1)',
            'rgba(52, 213, 94, 1)',
            'rgba(213, 52, 162, 1)',
        ]
    }

    Dashboard.prototype.options = {
        asianPopulation: {
            type: 'bar',
            labels: [],
            elements: {
                title: "세계 인구 현황",
                width: 4,
                desc: "세계인구 현황(남자/여자) 구별 안함",
                rows: 1
            },
            data: [{
                label: '세계인구',
                data: [],
            }]
        },
        asianPopulation2: {
            type: 'line',
            labels: [],
            elements: {
                title: "세계 인구 현황",
                width: 4,
                desc: "세계인구 현황(남자/여자) 구별 안함",
                rows: 1
            },
            data: [{
                label: '세계인구',
                data: [],
            }]
        },
        asianPopulation3: {
            type: 'pie',
            labels: [],
            elements: {
                title: "세계 인구 현황",
                width: 4,
                desc: "세계인구 현황(남자/여자) 구별 안함",
                rows: 1
            },
            data: [{
                label: '세계인구',
                data: [],
            }]
        }
    }
    Dashboard.prototype.getData = function () {
        const _this = this;
        this.getDataCallback = function (data) {
            if (data.length === 0) {
                return console.error("데이터가 존재하지 않습니다.", data)
            } else {
                _this.chartAppend(data);
            }
        }

        setTimeout(function () {
            _this.getDataCallback({
                asianPopulation: [{
                   data: [50, 500, 300, 200],
                   labels: ['한국', '중국', '러시아', '일본']
                }],
                asianPopulation2: [{
                   data: [50, 500, 300, 200],
                   labels: ['한국', '중국', '러시아', '일본']
                }],
                asianPopulation3: [{
                   data: [50, 500, 300, 200],
                   labels: ['한국', '중국', '러시아', '일본']
                }]
            });
        }, 1000);
    }

저희가 건드릴 거는 딱 2개입니다. 여기서 정말 객체지향 프로그래밍이 편하죠! 데이터만 받으면되는 환경을 만들고 그 환경도 수정하기 쉽게 분리를 해서 하면 데이터를 받아도 그 데이터만 쉽게 수정할 수 있어서 가독성과 편리성 둘 다 챙길 수가 있죠! 자 추가하고 싶은 차트만큼 getDataCallback에 만드시고 그 추가한 데이터만큼 차트 데이터도 만드시면 됩니다.

 

 

 

데이터만 추가할 뿐인데 차트가 알아서 잘 그려지네요. 이런 식으로 개발 자동화를 할 수 있습니다. 실무에서는 이렇게 자동화할 수 있고 분리할 수 있는 코드를 만들어야합니다. 딱 특정 기능만 하고 분리할 수 없는 코드는 실무에서는 못씁니다. 언제나 분리 할 수 있는 코드를 짜신 후에 프레임워크와 라이브러리를 배우시는 게 낫습니다. 무너질 거 같은 모래성에서 더 모래성만 쌓아봤자 무너지기 쉽습니다. 좀 더 기초에 신경 쓰셔서 코드를 만드세요. 그러면 라이브러리와 프레임워크를 쓸 때 더욱 강력한 힘을 발휘할 수 있습니다.

 

감사합니다.

 

참조 www.chartjs.org/docs/latest/getting-started/ 

반응형

댓글