Nextree

jqPlot으로 그래프 그리기!

Nextree Dec 28, 2013 0 Comments

글을 쓰기 전에..

입사 후 처음으로 참여한 프로젝트도 어느덧 마무리를 향하고 있고, 프로젝트를 진행하면서 여러 가지 기술들을 접해보고 배웠습니다. 그 중에서 가장 의미 있는 기술에 대해 글을 쓰려고 하는데, 처음으로 내가 알고 있는 기술을 다른 사람들에게 소개를 하게 되어 매우 설레는 마음으로 조심스레 글을 쓰려고 합니다. 이번 프로젝트를 진행하면서 나에게 가장 친근하고 가장 많이 사용했고 그 누구보다도 자신 있다고 생각하는 JQ-PLOT 이라는 그래프 만드는 Utility에 대해 소개를 하려고 합니다. 프로젝트 특성상 그래프는 광범위한 통계자료들을 한눈에 쉽게 파악 할 수 있도록 도와주는 핵심 기술 중에 하나라고 생각합니다. JQ-PLOT은 그래프를 쉽게 만들 수 있도록 도와주는 라이브러리이며 오픈소스로 누구나 제약 없이 사용할 수 있도록 제공하고 있습니다.

이 글을 쓰는 목적은 크게 3가지가 있으며, 그중 첫번째 목적은 다른사람들에게 그래프를 쉽게 만들 수 있도록 소개를 해주는 것입니다. 또한 글을 쓰면서 프로젝트중에 익힌 그래프 만드는 기술을 정리하는 시간을 가지는 두번째 목적, 마지막으로 나중에 다른프로젝트에서 그래프를 사용하게 될 때 참고하기 위한 기록의 의미를 담고있는 세번째 목적이 있습니다. 이 글을 통해서 이3가지 목적을 모두 달성하는 1석 3조의 효과를 얻고자 합니다. JQ-PLOT을 이용한 그래프의 종류는 다양하지만 이번에 소개할 그래프는 그중에서 가장 많이 사용해온 꺾은선 그래프에 대해서 다루도록 하겠고, 추후에 기회가 된다면 막대그래프, 도넛그래프 등등 소개 할 예정입니다.

JQ-PLOT의 구현방법

JQ-PLOT의 기본적인 구동 원리는 대부분 Browser에서 이루어지며, Server에서 들어온 Data를 Script내에서 필요한 Option들을 설정후에 Div로 그래프를 생성하여 보여주는 방식입니다.

JQ-PLOT 환경설정

JQ-PLOT는 J-QUERY 를 기반으로 구현된 Plugin이기 때문에 J-QUERY 와 JQ-PLOT 라이브러리를 홈페이지에서 다운을 받아야 합니다.( www.jquery.com , www.jqplot.com) 그리고 필요한 파일들을 Import 하면 그래프가 정상적으로 출력됩니다.

<!-- JQ-PLOT의 CSS를 설정 -->  
<link class="include" rel="stylesheet" type="text/css" href="jquery.jqplot.css"/>  
<!-- JQ-PLOT의 기본 설정 -->  
<script type="text/javascript" src="jquery.jqplot.js"/>  
<!-- Highlighter(마우스 접근시 데이터정보 표시) 설정 -->  
<script type="text/javascript" src="jqplot.highlighter.js"/>  
<!-- 좌표에 관한 정보나 Zoom 기능 사용시 설정 -->  
<script type="text/javascript" src="jqplot.cursor.js"/>  
<!-- 축의 데이터를 날짜형태로 입력하기 위해서 설정 -->  
<script type="text/javascript" src="jqplot.dateAxisRenderer.js"/>  
<!-- 축의 데이터의 Label Option을 설정 -->  
<script type="text/javascript" src="jqplot.canvasAxisLabelRenderer.js"/>  
<!-- Legend(Line에대한 간단한 범례)의 Option을 설정 -->  
<script type="text/javascript" src="jqplot.enhanceLegendRenderer.js"/>  
<!-- 축의 데이터를 순서에 상관없이 자동정렬을 설정 -->  
<script type="text/javascript" src="jqplot.categoryAxisRenderer.js"/>  
<!-- 축의 데이터 표현설정과 그래프위의 점의 Option을 설정 -->  
<script type="text/javascript" src="jqplot.canvasAxisTickRenderer.js"/>  

JQ-PLOT을 사용하며 겪은 소소한 경험담들..

우선 그래프를 구현하기 위해 필요한 기본 요소들는 그래프에 표현될 데이터를 Javascript 상에서 배열 형태로 적재(x,y 한쌍 단위로)가 되어 있어야 하며, 그래프를 보여줄 div가 있으면 기본적인 모습의 그래프를 구현할 수 있습니다.

<script type="text/javascript">  
  $(document).ready(function() {
     //X,Y 쌍으로 배열의 형태로 차례대로 값을 넣습니다.
     var line =[[1,3],[2,7],[3,9],[4,1],[5,4],[6,6],[7,8],[8,2],[9,5]];
     //id가 graphDiv인 곳에 그래프로 나타낼 Line을 넣어 표현한다.
     var plot = $.jqplot('graphDiv', [line]);
  });
</script>

...

<body>  
     //그래프를 출력할 Div를 생성
     <div id="graphDiv"></div>    
</body>  

기본적인 꺾은선 그래프 모습입니다.

실제로 프로젝트를 진행하면서 Controller에서 Map의 형태로 화면에 내보내고, 이를 화면에서 받아서 배열에 넣어 사용하니 정상적으로 실행되었으며 이를 발판으로 List로도 사용 가능합니다. 이는 Map에서는 key와 value 값으로 넘어온 값을 그래프로 표현하기에는 문제가 없었지만, 그래프상에서 2개 이상의 Line을 그리고 싶다면 Controller에서 Line의 개수만큼 Map을 만들거나 Map안에 Map을 넣는 복잡한 방법밖에는 없었습니다. 하지만 List를 사용하면 원하는 Type으로 List를 내보내게 되면 1개의 List로 그래프로 표현할 데이터를 선택해서 여러개의 Line을 쉽게 구현할 수 있어 획기적인 방법입니다.

개발 요구사항에 따라 X축의 데이터를 시간으로 표현을 해야만 했는데, List에 담겨있는 시간 데이터는 yyyyMMdd 의 String 형의 데이터였으며, 현재 상태로는 입력된 월/일/시간/분 이 Number로 인식되어 99월 99일 같은 Error 데이터도 발생합니다. 수많은 시행착오와 Google검색을 통해 시간입력 방법은 yyyy/MM/dd(yyyy/MM/dd HH:mm) 또는 yyyy-MM-dd (yyyy-MM-dd HH:mm)형식으로만 가능 하다는 사실을 깨달았습니다.

var line =[['2013/12/25',15],['2013/12/26',22],['2013/12/27',11],['2013/12/28',32],  
          ['2013/12/29',41],['2013/12/30',23]];

var plot = $.jqplot('graphDiv', [line],{  
          axes:{
            xaxis:{
                 // 날짜 형태로 입력을 하기위해서는 Date형식의 Renderer을 사용합니다.
                 renderer:$.jqplot.DateAxisRenderer,
                 tickOptions:{ // 축에관한 옵션                    
                     // 입력된 값이 날짜형태로 인식되기 위해서 format 형식을 정해주고 입력값도
                     // yyyy/mm/dd 형식으로 입력해야만 정상적으로 나타납니다.
                       formatString:'%y/%m/%d'
                 } 
             }
          }
    });

x 축을 날짜형식으로 변환한 모습입니다.

그래프위에 2개 이상의 Line이 존재할 경우 이를 구분하기가 어려워 Legend(범례)를 사용하였는데, Legend와 Line에 대한 설정을 바꾸기 위해서는 series라는 배열에 Line 개수만큼 선언합니다. 또한 Line 굵기와 Color 그리고 Legend에 보여질 Line의 Label과 마우스 접근시 해당 데이터의 상세정보를 확인 할 수 있는 Highrighter(좌표의 간략한 정보) 설정 등을 할 수 있어 Line들을 Legend를 보고 구분할 수 있게 됩니다.

title : '<< 짜장 vs 짬뽕 >>',  
axes:{ // 축 옵션  
    xaxis:{ // X축 옵션
               label : '날 짜',
        renderer:$.jqplot.DateAxisRenderer,
        tickOptions:{
            // X축의 글자 형식 설정 (년/월/일 시:분 으로 표기)
            formatString:'%y/%m/%d %H:%M', 
            // X축의 글자 각도
            angle : -90         
        } 
    },
    yaxis:{ // y축 옵션
                        label : '선호도', // y축 Label
             min : 0, // 최소값
             max : 100,  // 최대값
             numberTicks : 11, // 인위적으로 축을 나누는 개수
             tickOptions:{
             fontString : '%1f' // 소수점 1째 자리까지 표기
             }
     }
},
series : [ // Series 옵션  
  { //첫번째 Line에 관한 Series 옵션
  color : 'blue', //Line Color
  label : '자장면', // Line Label (Legend 설정시 표시되는 이름)
  lineWidth : 2, // Line 굵기
  markerOptions : { // 점 옵션
    size : '3px' // 점 Size
  },
  highlighter: { // 마우스 접근시 나타나는 정보 옵션
    formatString: // Highlighter 내부의 글자 형식 옵션
     // HTML Table을 이용하여 깔끔하게 구현(Highlighter 내부의 Color 와 글자 Size 조정
     // %s를 조회하면 차례대로 해당 좌표의 x값과 y값을 조회합니다. )
     '<table class="jqplot-highlighter">\
       <tr><td><font size="3px" color="black">자장면 선호도</font></td></tr>
       <tr><td><font size="3px" color="black">날짜 : %s</font></td></tr>
       <tr><td><font size="3px" color="black">선호도 :%s %</font></td></tr>
     </table>'
    }
  ],
  {//두번째 Line에 관한 Series 옵션
  color : 'red',
  label : '짬뽕',
  lineWidth : 2,
  markerOptions : {
    size : '3px'
  },
  highlighter: {
    formatString:
     '<table class="jqplot-highlighter">\
       <tr><td><font size="3px" color="black">짬뽕 선호도</font></td></tr>
       <tr><td><font size="3px" color="black">날짜 : %s</font></td></tr>
       <tr><td><font size="3px" color="black">선호도 :%s %</font></td></tr>
     </table>'
    }
  }
}
legend : { // Legend 옵션  
    renderer : $.jqplot.EnhancedLegendRenderer,
    show : true, // Legend 표시 유무
    placement : 'outside', // Legend 위치 (Default값은 inside)
    textColor : 'black',  // Legend 내부 Text Color
    rowSpacing : '0px',  // Legend 들간의 사이 공간
    location : 'ne'  // Legend 위치 (e,w,s,n)(동,서,남,북) 조합가능
}

여러가지 옵션들을 적용한 그래프를 구현한 모습입니다.

JQ-PLOT에서 x축의 값을 자동으로 Line의 해당 x축데이터를 기준으로 Setting이 됩니다. 그래프에 대한 요구사항이 점차 난해해 지면서 데이터의 개수가 많을때는 상관없지만, 데이터가 적은경우 (그래프에 표시된 Line의 점의 개수가 적을때) 날짜를 중복해서 여러개가 Setting되어 개선이 필요했습니다. 이 문제를 개선하는 방법을 JQ-PLOT에서 제공하지 않았고, 고민을 거듭한 끝에 축의 데이터를 인위적으로 Setting하는 Ticks라는 배열을 발견 했습니다. 이 Ticks배열에 시간 형식(yyyy/MM/dd HH:mm)으로 넣게 되면 배열에 들어있는 시간만 x축에 표현하여 자동으로 중복되는 날짜 문제를 해결할 수 있습니다.

Issue - x축에 중복되는 날짜가 발생합니다.

xaxis:{  
        label : '날 짜',
        renderer:$.jqplot.DateAxisRenderer,
        tickOptions:{  
            formatString:'%y/%m/%d %H:%M',             
                       angle : -90         
        },
        // x축에 Ticks를 걸어서 인위적으로 표시할 날짜를 설정합니다.
        // 실제 프로젝트 상에서는 데이터를 조회시 자동으로 해당 데이터에 대한 날짜를
        //  List로  화면에 보내서 ticks에 입력합니다.
        ticks : ['2013/12/01','2013/12/02','2013/12/03''2013/12/04']
}

Solution- 데이터가 적을 경우 개수만큼만 날짜 표시합니다.

그래프가 복잡해지면서 Line들이 여러개가 밀집되어있는 부분은 데이터를 판단하기 어려웠습니다. JQ-PLOT의 Option중에 Zoom 이라는 기능이 있는데 마우스로 확대하고 싶은 영역을 선택하여 확대를 하는 기능입니다. Zoom 기능은 JQ-PLOT에서 제공하는 획기적인 기능이지만, 결졍적으로 같은 부분을 계속 Zoom 하다보면 화면이 깨지는 현상이 계속 발생했고 Zoom기능은 JQ-PLOT에서 제공하는 기능이기에 소스를 개선할 방법이 없습니다. 고민끝에 축의 Min ,Max 값을 변경하여 마치 Zoom 기능인 것 처럼 사용자가 입력한 범위를 표현해주는 기능을 구현했습니다. 비록 마우스 Drag를 통한 간편한 방법은 아니지만, 사용자가 값의 범위를 직접 입력하는 방법으로 언제든지 반복적으로 사용할 수 있었습니다.

Issue - Line 이 밀집된 부분에서 데이터 판단하기 난해합니다.

//범위값 변경시 최대 최소값을 파라미터로 받는 function으로 구현합니다.
function drawGraph(yMin,yMax){ 

// 중요 : 변경이 된경우에는 기존에 Div에 남아있는 그래프를 지우고 다시 그려야 합니다.
$('#graphDiv').empty();

//그래프 구현

    ...

      yaxis:{ 
             label : '선호도', 

            // 최대 최소 값을 사용자가 원하는 값으로 입력합니다.
             min : yMin,             
             max : yMax,  
             numberTicks : 11,
             tickOptions:{
             fontString : '%1f'
             }
   }
}

Solution- 밀집된 부분만 확대해서 보는 기능을 추가했습니다.

데이터의 y축의 값이 0인경우 어떤 그래프에서는 0인시간대는 표현하지말고 직전 시간대와 직후 시간대의 점을 선으로 이어서 무시하고 지나가는 방법을 요구했습니다. 그래프의 Line 에서 해당시간대를 무시하도록 구현하는 방법을 찾다가 선호도가 0인 시간대를 조건문으로 판별하여 Line 배열에 넣지 않는 방법으로 해결했습니다.

Issue - 데이터가 없을 때 값이 0으로 입력되어 시각적으로 혼동을 발생시킵니다.

// 예를들어 Server에서 dataList의 이름으로 데이터를 보냈다고 가정하면
// dataList의 속성은 날짜(date)와 선호도(rate)가 있습니다.
var line = [  
  <c:forEach items="${dataList}" var = "stat">
     <c:choose>
        // rate가 0이 아닌 경우에만 Line에 넣어줌으로써 Line은 없는 데이터를 
              // 자연스럽게 지나치게 됩니다.  
        <c:when test = "${stat.rate ne 0}">
            ['${stat.date}',${stat.rate}],
        </c:when>
     </c:choose>
  </c:foreach>
];

Solution- 데이터가 없는 부분은 Line 생성시 제외시켰습니다.

다른 Issue 로는 그래프에는 Line 한줄에서 사용자가 인위적으로 그룹을 지어 중간에 줄을 끊어서 표현해달라는 요구사항이 들어왔습니다. 이는 그래프의 Line의 특성상 특정 점과 점 사이만 조정할 수 있는 방법이 없었는데, Option중에 Break On Null이라는 Option을 이용해서 y축값이 Null로 셋팅된 부분을 기점으로 그래프가 끊어지게 표현할 수 있게되었습니다.

Issue- Line 한개에서 시간대 별로 Group을 만들어도 같은 Line이므로 선이 연결되어 판단하기 힘듭니다.

series : [{

  // BreakOnNull은 Default값은 false 이며, True로 설정할 경우, 데이터의 y값이
    // Null로 셋팅된 부분을 기점으로 Line을 양쪽으로 끊습니다.
  // 따라서 Line을 나누려는 시간대에서 데이터가 없는 시간대의 y값을 Null로 채우면 됩니다.
  ...

  breakOnNull : true,

  ...
}]

Solution- Break On Null 을 이용하여 같은 Line상의 Group화를 구현했습니다.

글을 마치며..

데이터가 많이 누적되면서 그래프의 모습도 점점 진화하여 Open Source라고 생각하기 힘들 정도로 그럴듯한 모습의 그래프로 거듭났지만, 여전히 다른 그래프 Util에 비해서 다소 Quality가 부족합니다. 하지만 여기서 강조하는 사실은 JQ-PLOT은 오픈 소스이기 때문에 본인이 원한다면 장소에 구애받지 않고 다양한 그래프를 그려보고, 연구할 수 있는 장점이 있습니다. 많은 사람들이 그래프를 통계화면에서의 꽃이라고 부르는 만큼 여러 프로젝트를 진행하다보면 분명히 그래프를 구현해야 할 때가 한번쯤은 있을 것입니다. 물론 JQ-PLOT을 사용하지 않고 다른 Util을 이용해서 구현하는 경우도 있겠지만, JQ-PLOT만 익혀 놓는다면, 다른 그래프 Util들도 구현하는 기본 원리는 비슷하기 때문에 분명히 도움이 될 것이며, 그래프에 대한 자신감도 붙을 것입니다.

Nextree

Read more posts by this author.