Devme/Project

JAVA / SPRING 프로젝트 삼삼하개(5) CHART.JS를 통해 데이터정보 시각화

 

팀프로젝트가 끝나고 되돌아보는 시간 (5) CHART.JS를 통해 데이터정보 시각화

 

시각적 효과가 미치는 영향이 굉장히 크다고 생각하기 때문에 D3.JS, HIGHCHART.JS 등 다양한 라이브러리 중

개인적으로 가장 시각적으로 이뻐보이는 CHART.JS를 선택했습니다.

 

 

Chart.js 를 이용하여 데이터정보 시각화

 

관리자페이지 내 이용권 관리는 원래 결제목록(구매, 환불)만 확인 가능하도록 기획했었으나,

완성하고나니 페이지 구성이 부족한 느낌이 들어 차트를 추가 구현하게 되었습니다.

 

 

import (chart.js)  document🚀

<!-- 제이쿼리 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous" type="text/javascript"></script>
<!-- chart.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>

 

admin_pay.jsp(라인,바,도넛 3종 차트 canvas 생성)

<div class ="chartjs">
	<div class ="linechart"><canvas id="linechart" height="250" width="250"></canvas></div>
	<div class ="barchart"><canvas id="barchart" height="250" width="250"></canvas></div>
	<div class ="donutchart"><canvas id="donutchart" height="250" width="250"></canvas></div>
</div>

 

chart 생성 javascript

var chart1Labels = [];
var chart1adopt = [];
var chart1home = [];
var chart1free = [];

var chart2Labels = [];
var chart2data1 = [];
var chart2data2 = [];
var chart2data3 = [];
var chart2tooltip1 = [];
var chart2tooltip2 = [];
var chart2tooltip3 = [];

var chart3Labels = [];
var chart3data = [];

$(document).ready(function(){
	getTimeStamp();

	jQuery.ajax({ // $.ajax 와 동일한 표현
		url : '/samsam/storereport.do',
		type : 'POST',
		dataType : 'json', //서버에서 보내줄 데이터 타입
		contentType : 'application/json;charset=utf-8',
		success : function(map) {
			console.log(map);
			if(map.storecount != null || map.standbycount != null){
			 	var count = "<tr><td>총 가입업체 수</td><td>" + map.storecount+"</td></tr>"
			 		count += "<tr><td>인증대기업체 수</td><td>" + map.standbycount+"</td></tr>"
		
				   $('.storecount').append(count);  
			}//if
			if(map.chart1 != null && map.chart1 != ""){
				console.log("chart1")
				console.log(map.chart1)
			$.each(map.chart1, function(index, item){
				if(item.board == "분양"){
					chart1adopt.push(item.write_count);
					chart1Labels.push(item.write_date.substr(2,8));
				}
				if(item.board == "가정"){
					chart1home.push(item.write_count);
				}
				if(item.board == "책임"){
					chart1free.push(item.write_count);
				}
			});
			}//map.chart1
			console.log("chart2")
			console.log(map.adopt)
			console.log(map.home)
			console.log(map.free)
			
			if(map.adopt != null && map.adopt != ""){
				chart2Labels.push("분양")
				var maxitem =0;
				var max_local=null;
				var second = 0;
				var second_local=null;
				var minitem = 0;
				var min_local=null;
				$.each(map.adopt, function(index, item){
					console.log("인덱스 :"+index)
					
					 if (parseInt(item.alocal_count) > maxitem){
						 second = maxitem
						 second_local = max_local
						 maxitem = parseInt(item.alocal_count)
						 max_local = item.local
					 }else if(second < parseInt(item.alocal_count) < maxitem){
						 second = parseInt(item.alocal_count)
						 second_local = item.local
					 }else{
						 minitem = parseInt(item.alocal_count)
						 min_local = item.local
					 }
				});
				chart2data1.push(maxitem)
				 chart2tooltip1.push(max_local)
		         chart2data2.push(second)
				 chart2tooltip2.push(second_local)
			 	 chart2data3.push(minitem)
				 chart2tooltip3.push(min_local)
			}
			if(map.home != null && map.home != ""){
				chart2Labels.push("가정")
				var maxitem =0;
				var max_local=null;
				var second = 0;
				var second_local=null;
				var minitem = 0;
				var min_local =null;
				$.each(map.home, function(index, item){
					console.log("가정인덱스 :"+index)
					
					 if (parseInt(item.hlocal_count) > maxitem){
						 second = maxitem
						 second_local = max_local
						 maxitem = parseInt(item.hlocal_count)
						 max_local = item.local
					 }else if(second < parseInt(item.hlocal_count) < maxitem){
						 second = parseInt(item.hlocal_count)
						 second_local = item.local
					 }else{
						 minitem = parseInt(item.hlocal_count)
						 min_local = item.local
					 }
				});
				chart2data1.push(maxitem)
				 chart2tooltip1.push(max_local)
		         chart2data2.push(second)
				 chart2tooltip2.push(second_local)
			 	 chart2data3.push(minitem)
				 chart2tooltip3.push(min_local)
			}
			if(map.free != null && map.free != ""){
				chart2Labels.push("책임")
				var maxitem =0;
				var max_local =null;
				var second = 0;
				var second_local=null;
				var minitem = 0;
				var min_local =null;
				$.each(map.free, function(index, item){
					console.log("ㅊㅇ인덱스 :"+index)
					 if (parseInt(item.flocal_count) > maxitem){
						 second = maxitem
						 second_local = max_local
						 maxitem = parseInt(item.flocal_count)
						 max_local = item.local
					 }else if(second < parseInt(item.flocal_count) < maxitem){
						 second = parseInt(item.flocal_count)
						 second_local = item.local
					 }else{
						 minitem = parseInt(item.flocal_count)
						 min_local = item.local
					 }
				});
				chart2data1.push(maxitem)
				 chart2tooltip1.push(max_local)
		         chart2data2.push(second)
				 chart2tooltip2.push(second_local)
			 	 chart2data3.push(minitem)
				 chart2tooltip3.push(min_local)
			}//map.chart2
			console.log("chart3")
			console.log(map.chart3pay)
			console.log(map.chart3repay)
			
			var repayCount = null;
			if(map.chart3pay != null && map.chart3pay != ""){
				$.each(map.chart3pay, function(index, item){
					if(item.refund != null && item.refund !=""){
						chart3Labels.push(item.refund);
						chart3data.push(item.count)	
					}
				});
			}
			if(map.chart3repay != null && map.chart3repay != ""){
				$.each(map.chart3repay, function(index, item){
					repayCount += item.biz_count
					
					chart3Labels.push("Buy again")
					chart3data.push(repayCount)
				});
			}
			lineChart()
			barChart();
			donutChart()
			console.log("create Chart");
		},
		error : function() {
			console.log("업체카운팅 and chart ajax실패!!!");
		}
	});	

})//ready
function lineChart(){
	console.log("linechart")
	console.log(chart1adopt)
	console.log(chart1home)
	console.log(chart1free)
	console.log("툴팁")
	console.log(chart1Labels)
var ctx = $('#linechart').get(0).getContext('2d');
var linedata =
{
    labels: chart1Labels,
    datasets:
        [{
        	labels: '업체',
            borderColor: 'rgba(255, 99, 132, 1.5)',
            fill: false,
            data: chart1adopt
        },
        {
        	labels: '가정',
            borderColor: 'rgba(54, 162, 235, 1.5)',
            fill: false,
            data: chart1home
        },
        {
        	labels: '책임',
            borderColor: 'rgba(255, 206, 86, 1.5)',
            fill: false,
            data: chart1free
        }]
};
var lineoptions = {
		responsive: true,
		legend : {
			display: false
		},
		title: { 
			display: true, 
			text: '     일간분양게시현황', 
			fontSize: 17, 
			fontColor: 'rgba(46, 49, 49, 1)' 
		},
		animation: false,
		tooltips: {
            displayColors: false, // 툴팁 바 컬러 표시 여부
            titleFontColor: '#fff', // 툴팁 폰트 관련
            titleAlign: 'center', // 툴팁 폰트 관련
            bodyAlign : 'center',
            callbacks: {
                label: function(tooltipItem, data) {
                       var item = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
                       var label = data.datasets[tooltipItem.datasetIndex].labels;
                       return label + ' : '+item;
                  }
             }
    }
};
		
var lineChart = new Chart(ctx,{          
	type: 'line',
	data: linedata,
	options: lineoptions
});
}//line chart
function barChart(){
	console.log("barchart")
	console.log(chart2data1)
	console.log(chart2data2)
	console.log(chart2data3)
	console.log("툴팁")
    console.log(chart2tooltip1)
    console.log(chart2tooltip2)
    console.log(chart2tooltip3)

var ctx = $('#barchart').get(0).getContext('2d');
var bardata =
{
    labels: chart2Labels,
    datasets:
        [{
        	labels: chart2tooltip1,
            backgroundColor: 'rgba(255, 99, 132, 1)', 
            borderColor: 'rgba(255, 99, 132, 1.5)',
            data: chart2data1
        },
        {
        	labels: chart2tooltip2,
            backgroundColor: 'rgba(54, 162, 235, 1)', 
            borderColor: 'rgba(54, 162, 235, 1.5)',
            data: chart2data2
        },
        {
        	labels: chart2tooltip3,
            backgroundColor: 'rgba(255, 206, 86, 1)', 
            borderColor: 'rgba(255, 206, 86, 1.5)',
            data: chart2data3
        }]
};
var baroptions = { 
		responsive: true,
		legend : {
			display: false
		},
		title: { 
			display: true, 
			text: '   최근 1주간 게시글 지역 탑3', 
			fontSize: 17, 
			fontColor: 'rgba(46, 49, 49, 1)' 
		},
		animation: false,
		tooltips: {
            displayColors: false, // 툴팁 바 컬러 표시 여부
            titleFontColor: '#fff', // 툴팁 폰트 관련
            titleAlign: 'center', // 툴팁 폰트 관련
            callbacks: {
              label: function(tooltipItem, data) {
                     var item = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
                     var label = data.datasets[tooltipItem.datasetIndex].labels[tooltipItem.index];
                     return label + ' : '+item;
                }
           }
    }
};
		
var barChart = new Chart(ctx,{          
	type: 'bar',
	data: bardata,
	options: baroptions
});
}//bar chart

function donutChart(){
	console.log("donutchart")
	console.log(chart3data)
	console.log("툴팁")
	console.log(chart3Labels)
var ctx = $('#donutchart').get(0).getContext('2d');
var donutdata =
{
    labels: chart3Labels,
    datasets:
        [{
            backgroundColor: [
            	'rgba(255, 99, 132, 1.5)',
            	'rgba(54, 162, 235, 1.5)',
            	'rgba(255, 206, 86, 1.5)'
            ],
            hoverBorderColor : '#fff',
            data: chart3data
        }]
};
var donutoptions = { 
		responsive: true,
		legend : {
			display: true,
			position : 'bottom'
		},
		title: { 
			display: true, 
			text: '최근 한달 구매내역', 
			fontSize: 17, 
			fontColor: 'rgba(46, 49, 49, 1)' 
		},
		animation: false,
		tooltips: {
            displayColors: false, // 툴팁 바 컬러 표시 여부
            titleFontColor: '#fff', // 툴팁 폰트 관련
            titleAlign: 'center', // 툴팁 폰트 관련
            bodyAlign : 'center'
    }
};
		
var donutChart = new Chart(ctx,{          
	type: 'doughnut',
	data: donutdata,
	options: donutoptions
});
}//donut chart

 

 

처음 차트 내용의 구성은 아래와 같이 하려했습니다만,

 

차트 1 ) 분양게시글 증감현황 (업체/가정/책임)  - 콤보 바라인 차트
차트 2 ) 구매 After (환불/재구매/유지) - 도넛
차트 3 ) 이용권 구매현황 (툴팁 - 매출지역top3 노출) - 라인

 

차트 1 은 증감을 시각화할때의 구체적인 내용이 결국 게시글 갯수를 라인차트로 표현하면 될 것 같았고,

차트 2 와 3은 배치순을 일/주/월 순으로 나열하는 것이 좋을 것 같아 수정하였습니다.

 

1. 일간분양게시현황(업체/가정/책임) - 라인차트(툴팁 - 게시판이름, 게시글갯수)
2. 최근 1주간 게시글 지역 탑 3 - 바
3. 최근 한달 구매내역 - 도넛

 

chart를 구성할 데이터를 ajax로 불러오고 미리 선언해둔 배열 변수에 데이터를 넣어줍니다.

중간중간 console.log로 제대로 들어갔는지 확인하면서 진행했습니다.

 

Controller (chart를 구성할 데이터)

 @RequestMapping(value = "/storereport.do")
 @ResponseBody
 public Map<String, Object> storereport(Model model) {
    Map<String, Object> map = new HashMap<String, Object>();
    ArrayList<ChartjsVO> ncvo = new ArrayList<ChartjsVO>();
	      
    int storecount = adminSV.storecount();
    int standbycount = adminSV.standbycount();
    //chart1
    ArrayList<TboardVO> chart1 = adminSV.getThreeCount();
    //chart2  
	ArrayList<ChartjsVO> adopt = adminSV.weeklyLocala();
	ArrayList<ChartjsVO> home = adminSV.weeklyLocalh();
	ArrayList<ChartjsVO> free = adminSV.weeklyLocalf();
	//chart3
	ArrayList<ChartjsVO> chart3pay = adminSV.payedMonth();
	ArrayList<ChartjsVO> chart3repay = adminSV.repayedMonth();
	      
	map.put("storecount", storecount);
	map.put("standbycount", standbycount);
	map.put("chart1", chart1);
	map.put("adopt", adopt);
	map.put("home", home);
	map.put("free", free);
	map.put("chart3pay", chart3pay);
	map.put("chart3repay", chart3repay);
	      
	return map;
  }

 

Mapper (Mybatis)

<!-- 인증대기업체 숫자 | 총 분양업체 숫자 -->
<select id="storecount" resultType = "int">
	select count(*) from biz_member where status = '0'
</select>
<select id="standbycount" resultType = "int">
	select count(*) from biz_member where status = '1'
</select>

<!-- chartjs 차트 1 ) 일간분양게시현황(업체/가정/책임) - 라인차트(툴팁 - 탑 3 작성자, 작성글갯수 요건 생각좀) -->
<insert id="countAdopt"><!-- 업체 --> 
	<selectKey keyProperty="count" resultType="int" order="BEFORE">
		select count(*) from adopt_list where doc_date = TO_CHAR(SYSDATE -1 ,'yyyy-mm-dd')
	</selectKey>
		insert into threeboard(write_date, write_count, board) values(SYSDATE -1, #{count}, '분양')
</insert>

<insert id="countHome"><!-- 가정 -->
	<selectKey keyProperty="count" resultType="int" order="BEFORE">
		select count(*) from adopt_home where doc_date = TO_CHAR(SYSDATE -1 ,'yyyy-mm-dd')
	</selectKey>
		insert into threeboard(write_date, write_count, board) values(SYSDATE -1, #{count}, '가정')
</insert>

<insert id="countFree"><!-- 책임 -->
	<selectKey keyProperty="count" resultType="int" order="BEFORE">
		select count(*) from free_doc where doc_date = TO_CHAR(SYSDATE -1 ,'yyyy-mm-dd')
	</selectKey>
		insert into threeboard(write_date, write_count, board) values(SYSDATE -1, #{count}, '책임')
</insert>

<select id="getThreeCount" resultType = "TboardVO"><!-- 업체 -->
	select * from threeboard where write_date >= TO_CHAR(sysdate -7, 'yyyy-mm-dd')
</select>

<!-- chartjs 차트 2 ) 최근 1주간 게시글 지역 탑 3 - 바 -->
<select id="weeklyLocala" resultType = "ChartjsVO"><!-- 업체 -->
	select rownum, local, alocal_count from 
    (select m.local, count(m.local) as alocal_count from member_list m,
	(select * from adopt_list where doc_date >= TO_CHAR(sysdate -7, 'yyyy-mm-dd')) a
	where m.email = a.doc_email group by m.local order by alocal_count desc)
    where rownum between 1 and 3
</select>

<select id="weeklyLocalh" resultType = "ChartjsVO"><!-- 가정 -->
	select rownum, local, hlocal_count from 
	(select m.local, count(m.local) as hlocal_count from member_list m,
	(select * from adopt_home where doc_date >= TO_CHAR(sysdate -7, 'yyyy-mm-dd')) a
	where m.email = a.doc_email group by m.local order by hlocal_count desc)
	where rownum between 1 and 3
</select>

<select id="weeklyLocalf" resultType = "ChartjsVO"><!-- 책임 -->
	select rownum, local, flocal_count from 
	(select m.local, count(m.local) as flocal_count from member_list m,
	(select * from free_doc where doc_date >= TO_CHAR(sysdate -7, 'yyyy-mm-dd')) a
	where m.email = a.doc_email group by m.local order by flocal_count desc)
	where rownum between 1 and 3
</select>

<!-- chartjs 차트 3 ) 최근한달 구매내역 (환불/구매) - 도넛 -->
<select id="payedMonth" resultType = "ChartjsVO">
	select refund, count(*) as count from payed_list where sysdate >= TO_CHAR(add_months(sysdate,-1)) group by refund
</select>

<select id="repayedMonth" resultType = "ChartjsVO"> <!-- 재구매 -->
	select biz_email, count(biz_email) as biz_count from (select * from payed_list where refund ='payed' and sysdate >= TO_CHAR(add_months(sysdate,-1)))
	group by biz_email having count(biz_email) >= 2 
</select>