본문 바로가기

꿀팁 활용

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

이번에는 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