꿀팁 활용

[javascript] c3.js를 사용하여 그래프를 다뤄보자

jeongyj 2022. 4. 6. 16:52

이번에는 c3.js를 사용하여 그래프를 다루는 방법에 대해서 알아보도록 하겠습니다. 우선 c3.js는 d3.js에 의존적인 라이브러리 입니다.

우선 c3는 다음을 의미합니다

1. Comfortable

2. Customizable

3. Controllable

d3는 다음을 의미합니다

Data-Driven Document

이 둘의 용어를 비교하면 c3.js가 먼가 더 편안한 느낌이 듭니다. 제 개인적으로도 c3.js가 사용하기 좀더 편한 느낌을 받았습니다.

c3.js를 사용하기 위해서는 js파일과 css 파일을 가져와야 합니다.

 

 

● 라이브러리 가져오기

css파일과 js파일을 가져와야 합니다. 해당 파일을 가지고 있지 않다면 CDN 서버에서 파일을 가져와도 됩니다.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.18/c3.min.css">

<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.11/c3.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
 

 

cs.js는 압축파일과 압축하지 않은 파일모두 제공을 합니다. c3를 사용하기 위해서는 d3가 있어야 합니다.

● 차트 생성하기

<body>
    <div id="chart"></div>
    <script>
        var chart = c3.generate({
             bindto: '#chart',
             data: {
               columns: [
                 ['data1', 30, 200, 100, 400, 150, 250],
                 ['data2', 50, 20, 10, 40, 15, 25]
               ]
             }
         });
    </script>
</body>

 

 

 

실행결과입니다. 간단한 코드를 이용하여 그래프를 그려보았습니다. 하지만 c3.js는 해당 그래프에 마우스를 올린다면 다음과 같이 정보를 표현합니다.

 

 

각 데이터가 어떤 값을 가지고 있는지 보여줍니다. 이 부분 때문에 css 파일을 로드합니다.

 

● generate 메소드 알아보기

 

generate 메소드를 호출하여 우리가 만들고자하는 그래프를 정의할 수 있습니다.

 

var chart = c3.generate({
    bindto: '#chart',
    data: {
        columns: [
            ['data1', 30, 200, 100, 400, 150, 250],
            ['data2', 50, 20, 10, 40, 15, 25]
        ]
    }
 });

   

· bindto

 

bindto는 그래를 그리는 위치입니다.

 

· 데이터 셋

data는 그래프에 표시될 데이터들입니다. data는 다양한 키들로 구성되어 있습니다.

columns

columns키는 그래프에 그려지는 데이터 셋 입니다. columns는 중첩된 리스트의 형태로 이루어져 있습니다. 내부에 있는 리스트들은 독립적인 데이터가 됩니다. data1에 대한 데이터, data2에 대한 데이터 더 추가를 하고 싶다면 리스트를 더 추가해주면 그래프가 추가적으로 그려집니다.

※ json

중첩된 리스트 형태로 작성될 필요는 없습니다. 개인적으로 중첩된 리스트의 형태보다는 다음과 같은 형태를 더 선호합니다.

 

var chart = c3.generate({
            bindto: '#chart',
            data:{
                json:{
                    data1: [ 30, 200, 100, 400, 150, 250],
                    data2: [50, 20, 10, 40, 15, 25]
                }
              }
        });

 

columns 대신 josn 을 활용하면 데이터를 json의 형태로 넣게 됩니다. 개인적으로는 이 방법을 더 선호합니다. 

data는 json, columns와 같은 데이터 셋 이외의 다양한 그래프 설정을 해주는 키값들이 존재합니다. 

※ type, types

type, types는 어떤 형태로 그래프를 그려줄지에 대한 정의입니다.

 

var chart = c3.generate({
            bindto: '#chart',
            data:{
                json:{
                    data1: [ 30, 200, 100, 400, 150, 250],
                    data2: [50, 20, 10, 40, 15, 25]
                },
                types:{
                    data1: 'bar',
                    data2: 'line'
                }
              }
        });

 

data1에 해당하는 그래프는 막대 그래프, data2에 해당하는 그래프는 선을 이용하여 표현합니다.

 

짜짠! 잘 그려졌습니다. 자 그런데 만약 데이터가 5종류가 되는데 하나만 다른 그래프고 나머지는 같은 그래프라면 일일히 다 설정을 해주어야 할까요? 아닙니다. 이땐 type을 적절히 이용하면 간단하게 코드를 작성할 수 있습니다.

 

var chart = c3.generate({
            bindto: '#chart',
            data:{
                json:{
                    date: ['20171101', '20171102', '20171103', '20171104', '20171105', '20171106'],
                    data1: [30, 200, 100, 400, 150, 250],
                    data2: [50, 20, 10, 40, 15, 25],
                    data3: [150, 120, 110, 140, 115, 125]
                },
                x: 'date',
                type: 'line',
                types:{
                    data1: 'bar',
                }
              }
        });

 

type을 line으로 설정했습니다. 이것은 기본적으로 line의 형태로 그래프를 그리라는 것 입니다. 하지만 types에서 data1은 bar그래프로 그리라고 설정했기 때문에 data1에 대한 그래프만 bar 그래프로 표현합니다.

 

멋진 그래프가 그려졌습니다. 

※ x

다음으로는 x 축에 대한 정보입니다. 만약 x축이 1, 2, 3, 4, 5와 같은 연속적인 숫자가 아닌 특정 데이터라면 어떻게 해야할까요? 이때는 x를 이용합니다.

 

var chart = c3.generate({
            bindto: '#chart',
            data:{
                json:{
                    date: ['20171101', '20171102', '20171103', '20171104', '20171105', '20171106'],
                    data1: [30, 200, 100, 400, 150, 250],
                    data2: [50, 20, 10, 40, 15, 25],
                    data3: [150, 120, 110, 140, 115, 125]
                },
                x: 'date',
                type: 'line',
                types:{
                    data1: 'bar',
                }
              }
        });

 

x축을 date를 이용하여 설정하겠다는 의미입니다.

 

· 격자표시

 

grid를 이용하면 그래프에 격자표시를 할 수 있습니다.

 

var chart = c3.generate({
            bindto: '#chart',
            data:{
                json:{
                    date: ['20171101', '20171102', '20171103', '20171104', '20171105', '20171106'],
                    data1: [30, 200, 100, 400, 150, 250],
                    data2: [50, 20, 10, 40, 15, 25],
                    data3: [150, 120, 110, 140, 115, 125]
                },
                x: 'date',
                type: 'line',
                types:{
                    data1: 'bar',
                }
            },
            grid: {
                x: {
                    show: true
                },
                y: {
                    show: true
                }
            }
        });
 
 

x축과 y축 show의 값에 따라서 특정 축의 격자표시만 할 수 있습니다.

· 축 설정

axis를 이용하여 축을 설정 할 수 있습니다.

 

var chart = c3.generate({
            bindto: '#chart',
            data:{
                json:{
                    date: ['20171101', '20171102', '20171103', '20171104', '20171105', '20171106'],
                    data1: [30, 200, 100, 400, 150, 250],
                    data2: [50, 20, 10, 40, 15, 25],
                    data3: [150, 120, 110, 140, 115, 125]
                },
                x: 'date',
                type: 'line',
                types:{
                    data1: 'bar',
                }
            },
            axis: {
              x: {
                tick: {
                    format: function(d) { console.log(d); return d}
                }
              },
              y: {
                tick: {
                    format: function(d){ return d}
                }
              }
            },
            grid: {
                x: {
                    show: true
                },
                y: {
                    show: true
                }
            }
        });

axis는 내부적으로 x와 y의 키값을 가지고 있습니다. 즉, x축과 y축 각각 설정이 가능합니다. tick은 축에 찍히는 값을 의미합니다. tick은 format이라는 키를 가지고 있습니다. format을 통해서 각 축에 데이터를 어떤식으로 띄울지 설정할 수 있습니다.

axis: {
              x: {
                tick: {
                    format: function(d) {  return `${d.toString().slice(0, 4)}년`;}
                }
              },
              y: {
                tick: {
                    format: function(d){ return d}
                }

              }
            }

 

format에 콜백 함수를 설정해도 되지만 데이터를 좀만 수정하여 넣는다면 귀찮게 toString이나 slice와 같은 함수를 사용하지 않아도 됩니다.

 

 

json:{
    date: ['2017-11-01', '2017-11-02', '2017-11-03', '2017-11-04', '2017-11-05', '2017-11-06'],
    data1: [30, 200, 100, 400, 150, 250],
    data2: [50, 20, 10, 40, 15, 25],
    data3: [150, 120, 110, 140, 115, 125]
},
axis: {
    x: {
        type:'timeseries',
            tick: {
                count: 1,
                format: '%Y'
            }
        },
   y: {
       tick: {
           format: function(d){ return d}
       }

    }
}

y축은 다른 형태로 수정해보겠습니다.

axis: {
    x: {
        type:'timeseries',
        tick: {
            count: 1,
            format: '%Y'
        }
    },
    y: {
        tick: {
            format: d3.format("$,")
        }

    }
}

● 다양한 차트의 형태 -1

data:{
    json:{
        date: ['2017-11-01', '2017-11-02', '2017-11-03', '2017-11-04', '2017-11-05', '2017-11-06'],
        data1: [30, 200, 100, 400, 150, 250],
        data2: [50, 20, 10, 40, 15, 25],
        data3: [150, 120, 110, 140, 115, 125]
    },
    x: 'date',
    types:{
        data1: 'spline',
        data2: 'area',
        data3: 'scatter'
    }
}

● 다양한 차트의 형태 - 2

data:{
    json:{
        date: ['2017-11-01', '2017-11-02', '2017-11-03', '2017-11-04', '2017-11-05', '2017-11-06'],
        data1: [30, 200, 100, 400, 150, 250],
        data2: [50, 20, 10, 40, 15, 25],
        data3: [150, 120, 110, 140, 115, 125]
    },
    x: 'date',
    type: 'pie'
}

● 다양한 차트의 형태 - 2

data:{
    json:{
        date: ['2017-11-01', '2017-11-02', '2017-11-03', '2017-11-04', '2017-11-05', '2017-11-06'],
        data1: [30, 200, 100, 400, 150, 250],
        data2: [50, 20, 10, 40, 15, 25],
        data3: [150, 120, 110, 140, 115, 125]
    },
    x: 'date',
    type: 'donut'
}

 

● 차트의 혼합

var chart = c3.generate({
            bindto: '#chart',
            data: {
                columns: [
                    ['data1', 30, 20, 50, 40, 60, 50],
                    ['data2', 200, 130, 90, 240, 130, 220],
                    ['data3', 300, 200, 160, 400, 250, 250],
                    ['data4', 200, 130, 90, 240, 130, 220],
                    ['data5', 130, 120, 150, 140, 160, 150],
                    ['data6', 90, 70, 20, 50, 60, 120]
                ],
                type: 'bar',
                types: {
                    data3: 'spline',
                    data4: 'line',
                    data6: 'area',
                },
                groups: [
                    ['data1','data2']
                ]
            },
            grid: {
                x: {
                    show: true
                },
                y: {
                    show: true
                }
            }
        });

 

● 이벤트 설정

data: {
                columns: [
                    ['data1', 30, 20, 50, 40, 60, 50],
                    ['data2', 200, 130, 90, 240, 130, 220],
                    ['data3', 300, 200, 160, 400, 250, 250],
                    ['data4', 200, 130, 90, 240, 130, 220],
                    ['data5', 130, 120, 150, 140, 160, 150],
                    ['data6', 90, 70, 20, 50, 60, 120]
                ],
                type: 'bar',
                types: {
                    data3: 'spline',
                    data4: 'line',
                    data6: 'area',
                },
                groups: [
                    ['data1','data2']
                ],
                oninit: function(d){console.log('init')},
                onmouseover: function(d){console.log('onmouseover'); console.log(d)},
                onmouseout: function(d){console.log('onmouseout'); console.log(d);},
                onresize: function(d){console.log('onresize'); console.log(d);},
                onresized: function(d){console.log('onresized'); console.log(d);},
                onrendered: function(d){console.log('onrendered');  console.log(d)}
            }
 

이벤트 부분은 자바스크립트를 좀 써봤다면 어느정도 느낌이 올 것이다. 해당 코드를 직접 실행하면서 콘솔창을 확인하면 바로 알 수 있는 부분입니다.

그래프에서 마우스를 이리저리 옮기면 해당 그림처럼 출력되는것을 확인할 수 있습니다.

문서를 보면 oninit, onresize, onresized, onrendered는 차트가 생성되거나 화면 사이즈가 변하는 중, 사이트가 완전히 변했을 때 이벤트가 발생한다고 되있는데, 필자의 컴퓨터에서는 정상적으로 작동이 되지않았습니다. mouseover와 mouseout만 정상작동 되네요 ㅋㅋㅋ

★ 실습

<script>

    let chart1;
    let chart2;
    let chart1Map = new Map();
    let chart2Map = new Map();

    $(function(){
        // 초기화
        /*
        $('#header').load("layout/header.html");
        $('#navbar').load("layout/navbar.html");
        $('#footer').load("layout/footer.html");
         */
        // 차트 01
        chart1 = c3.generate({
            bindto: "#item-graph01",
            size: {
                height: 250
            },
            data: {
                columns: [
                    ["요청", 0, 0, 0, 0, 0],
                    ["완료", 0, 0, 0, 0, 0],
                ],
                types: {
                    완료: "line",   //  bar or line 으로 차트 형태 설정
                    요청: "line",
                },
                colors: {
                    완료: "#ffe082",
                    요청: "#ffc000",
                },
                labels: {
                    format: {
                        // 매입: function (v, id, i, j) { return v + "건"; },
                        // 요청: function (v, id, i, j) { return v + "건"; },
                    }
                },
                groups: [
                    ['완료', '요청']
                ]
            },
            tooltip: {
                contents: function (d, defaultTitleFormat, defaultValueFormat, color) {

                    var $$ = this, config = $$.config,
                        titleFormat = config.tooltip_format_title || defaultTitleFormat,
                        nameFormat = config.tooltip_format_name || function (name) { return name; },
                        valueFormat = config.tooltip_format_value || defaultValueFormat,
                        text, i, title, value, name, bgcolor;
                    for (i = 0; i < d.length; i++) {
                        if (! (d[i] && (d[i].value || d[i].value === 0))) { continue; }

                        if (! text) {
                            title = titleFormat ? titleFormat(d[i].x) : d[i].x;
                            text = "<table class='" + $$.CLASS.tooltip + "'>" + (title || title === 0 ? "<tr><th colspan='2'>" + title + "</th></tr>" : "");
                        }

                        name = nameFormat(d[i].name);
                        value = valueFormat(d[i].value, d[i].ratio, d[i].id, d[i].index);
                        bgcolor = $$.levelColor ? $$.levelColor(d[i].value) : color(d[i].id);

                        text += "<tr class='" + $$.CLASS.tooltipName + "-" + d[i].id + "'>";
                        text += "<td class='name'><span style='background-color:" + bgcolor + "'></span>" + name + "</td>";
                        text += "<td class='value'>" + value + "건 </td>";
                        text += "</tr>";
                    }
                    //console.log('d -> ' + d);
                    //chart1Map.set(innerData.index + "_buy_cnt" , innerData.buyCnt);
                    //chart1Map.get()
                    /*
                    text += "<tr class='total'>";
                    text += "<td class='name'>" + '매입' + "</td>";
                    text += "<td class='value'>" + '9,990' + "건 </td>";
                    text += "</tr>";

                    text += "<tr class='total'>";
                    text += "<td class='name'></td>";
                    text += "<td class='value'>" + '9,990' + "원 </td>";
                    text += "</tr>";
                    */

                    return text + "</table>";
                }
            },
            // jyj 수정=============== responsive: false, 추가 (가로줄 확장)
            responsive: false,
            bar: {
                width: {
                    ratio: 0.4,
                },
            },
            transition: {
                duration: 1000
            },
            padding: {
                bottom: 0
            },

            axis: {
                x: {
                    show: true,

    
                     type: "category",  //그룹 막대일때 지정 밑 categories에서 x축 칼럼을 설정할수 있음
                    // categories: ['1st', '2nd', '3rd', '4th', '5th'],
                    // categories: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
                    //     11, 12, 13, 14, 15, 16, 17, 18, 19,
                    //     20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,31],
                    categories: [1,"" ,"" ,"" , 5,"" , "", "", "", 10,
                        "", "", "", "", 15, "", "", "", "",
                        20, "", "", "", "", 25, "", "", "", "", 30,""],
                    min: 0
                },
                y: {
                    show: false,
                    max: 100,
                    min: 0,
                    padding: {
                        bottom: 0
                    }
                }
            },
            legend: {
                show: false
            }

        });

        // 차트 02
        chart2 = c3.generate({
            bindto: "#item-graph02",
            size: {
                height: 250
            },
            data: {
                columns: [
                    ["판매중", 0, 0, 0, 0, 0],
                    ["매각", 0, 0, 0, 0, 0],
                ],
                types: {
                    매각: "line",
                    판매중: "line",
                },
                colors: {
                    매각: "#82d8a9",
                    판매중: "#00b050",
                },
                labels: {
                    format: {
                        // 매입: function (v, id, i, j) { return v + "건"; },
                        // 요청: function (v, id, i, j) { return v + "건"; },
                    }
                },
                groups: [
                    ['매각', '판매중']
                ]
            },
            tooltip: {
                format: {
                    title: function (d) { return 'data ' + d; },
                    value: function (value, ratio, id) {

                        //console.log("" + value + " , " + ratio + " , " + id);

                        var format = id === '매각' ? '건' : '건';
                        return value + format;
                    },
                }
            },
            //responsive: false, 추가 (가로줄 확장) 크기 설정 가능
            //ex <div class="graph" id="item-graph01" width="500vw" height="500vh"></div>
            // vw,vh 는 반응형일때 아니라면 그냥 px
            responsive: false,
            bar: {
                width: {
                    ratio: 0.4,
                },
            },
            axis: {
                x:  {

                    show: true,
                     type: "category",  //그룹 막대일때 지정 밑categories에서 x축 칼럼을 설정할수 있음
                    categories: [1,"" ,"" ,"" , 5,"" , "", "", "", 10,
                        "", "", "", "", 15, "", "", "", "",
                        20, "", "", "", "", 25, "", "", "", "", 30,""],
                    min: 1 //최소값 설정
                },
                y: {
                    show: false,
                    max: 100,
                    min: 0, //최소값 설정
                    padding: {
                        bottom: 0
                    }
                }
            },
            legend: {
                show: false
            },
            transition: {
                duration: 1000
            },
        });
        /*
        setTimeout(function () {
            chart1.load({
                columns: [
                    ["요청", 20, 30, 50, 60, 70],
                    ["완료", 30, 35, 65, 80, 85],
                ],
            });
            chart2.load({
                columns: [
                    ["판매중", 20, 30, 50, 60, 70],
                    ["매각", 30, 35, 65, 80, 85],
                ],
            });
        }, 300);
         */

        Service.getC3Data();


        $(".datepicker").datepicker({
            showOn: "both",
            buttonImage: "/assets/img/date_range.png",
            buttonImageOnly: true,
            dateFormat: 'yy-mm-dd',
            monthNames: ['1월','2월','3월','4월','5월','6월','7월','8월','9월','10월','11월','12월'],
            dayNamesMin: ['일','월','화','수','목','금','토'],
            dayNames: ['일요일','월요일','화요일','수요일','목요일','금요일','토요일'],
            onSelect: function() {
                searchDate = $.datepicker.formatDate("yy-mm-dd",$("#datepicker").datepicker("getDate"));
                // alert(searchDate);
                console.log(searchDate);
            }
        });


        $('#btnSearch').click(function () {
            Service.getC3Data_new();
        });



    });



    function getToday(){
        var date = new Date();
        var year = date.getFullYear();
        var month = ("0" + (1 + date.getMonth())).slice(-2);
        var day = ("0" + date.getDate()).slice(-2);

        return year + "-" + month + "-" + day;
    }


    let chart1_requestList = [];
    let chart1_finishList = [];
    let chart2_sellingList = [];
    let chart2_sellList = [];
    let obj = {};
    let searchDate = getToday();

    let Service = {
        getC3Data: function () {
            var url = "/api/admin/v1/dashboard";
            var ajaxParam = {
                "url": url,
                "type": "GET",
                "data": ""
            }
            Common.ajaxJSONV2(ajaxParam, function (data) {
                if (!data) {
                    alert('return data error');
                    return false;
                }
                if (data.result != 0) {
                    alert(data.message);
                    return;
                } else {
                    //alert(data.message);
                    chart1_requestList.push('요청');
                    chart1_finishList.push('완료');
                    chart2_sellingList.push('판매중');
                    chart2_sellList.push('매각');
                    console.log(data.data.weekly);
                    for (let i = 0; i < data.data.weekly.length; i++) {
                        let innerData = data.data.weekly[i];
                        /*
                        chart1_requestList.push((i+1) * 10);
                        chart1_finishList.push((i+1) * 10);
                        chart2_sellingList.push((i+1) * 10);
                        chart2_sellList.push((i+1) * 10);
                        */

                        chart1_requestList.push(innerData.requestCnt);
                        chart1_finishList.push(innerData.finishCnt);
                        chart2_sellingList.push(innerData.sellingCnt);
                        chart2_sellList.push(innerData.sellCnt);

                        chart1Map.set(innerData.index + "_buy_cnt" , innerData.buyCnt);
                        chart1Map.set(innerData.index + "_buy_money" , innerData.buyMoney);

                        //console.log(innerData.index + ' ' + innerData.requestCnt);
                    }
                    console.log('chart1_requestList ' + chart1_requestList);
                    console.log('chart1_finishList ' + chart1_finishList);
                    console.log('chart2_sellingList ' + chart2_sellingList);
                    console.log('chart2_sellList ' + chart2_sellList);
                    chart1.load({
                        columns: [
                            chart1_requestList,
                            chart1_finishList,
                        ],
                    });
                    chart2.load({
                        columns: [
                            chart2_sellingList,
                            chart2_sellList,
                        ],
                    });
                    
                    $('#guide_request_cnt').text(Common.comma(Number(data.data.todayGuideRequestCnt)));
                    $('#guide_finish_cnt').text(Common.comma(data.data.todayGuideFinishCnt));
                    $('#guide_ing_cnt').text(Common.comma(data.data.todayGuideRequestCnt));
                    $('#guide_buy_cnt').text(Common.comma(data.data.todayGuideBuyCnt));
                    $('#guide_buy_money').text(Common.comma(data.data.todayGuideBuyMoney));
                    $('#guide_selling_cnt').text(Common.comma(data.data.todayGuideSellingCnt));
                    $('#guide_sell_cnt').text(Common.comma(data.data.todayGuideSellCnt));
                    $('#guide_sell_money').text(Common.comma(data.data.todayGuideSellMoney));

                }
                return;
            });
        },

 

 

★ 데이터 형

 

 

출처 : https://m.blog.naver.com/pjt3591oo/221143761062