Angular.js 기반 개발시 시각화를 위하여 구글 차트를 이용할 경우 Directive화 방법
구글
- Don't invent wheel, already exist
- 하지만 자신의 입맛에 맞게 수정은 해야 한다.
- 수정하려면 GitHub에서 Fork한 후에 수정을 하자
https://github.com/mobiconsoft/angular-google-chart-resize
구글 차트 API 사용법
- 구글 Visualization 라이브러리위에 구글 Chart가 올라가는 형태이다 (참조)
- visualization의 버전 1.0 을 명시하고 사용할 차트 패키지를 설정한다. 보통 corechart 로 하면 bar, line, pie등을 사용할 수 있다
google.load('visualization', '1.0', {'packages':[<<list-of-google-chart-libraries>>]})
- 데이터 생성은 2차원의 google.visualization.DataTable을 이용한다
- https://google-developers.appspot.com/chart/interactive/docs/datatables_dataviews
- anuglar의 controller에서 하기와 같이 json형태로 정의 가능. 또는 addColumn() 호출 설정방식중 택일
- DataTable을 통해 .csv 파일 생성가능
$scope.chartObject = {};
$scope.onions = [
{v: "Onions"},
{v: 3},
];
// 데이터 시각화를 위하여 가장 큰 값은 첫번째에, 그 다음 큰값은 맨 마지막에 오게 한다
$scope.chartObject.data = {"cols": [
{id: "t", label: "Topping", type: "string"},
{id: "s", label: "Slices", type: "number"}
], "rows": [
{c: [
{v: "Olives"},
{v: 31}
]},
{c: $scope.onions},
{c: [
{v: "Zucchini"},
{v: 1},
]},
{c: [
{v: "Pepperoni"},
{v: 2},
]},
{c: [
{v: "머쉬롬"},
{v: 10},
]},
]};
$scope.chartObject.options = {
'title' : 'How Much Pizza I Ate Last Night',
'height' : 500,
'colors' : ['#e0440e', '#e6693e', '#ec8f6e', '#f3b49f', '#f6c7b6']
}
- 차트 커스터마이징을 위하여 옵션을 준다
- 높이는 px(pixel) 단위이다.
- 차트의 width + height 주는 방법은 html에 주거나, option 으로 주는 두가지 방법이 있다. (참조)
var options = {
'legend':'left',
'title':'My Big Pie Chart',
'is3D':true,
'width':400,
'height':300
}
- ChartWrapper를 이용하면 차트 종류, 데이터, 옵션, 그려지는 위치등의 설정을 한번에 할 수 있다 (ChartWrapp API)
var wrapper = new google.visualization.ChartWrapper({
chartType: 'ColumnChart',
dataTable: [['', 'Germany', 'USA', 'Brazil', 'Canada', 'France', 'RU'],
['', 700, 300, 400, 500, 600, 800]],
options: {'title': 'Countries'},
containerId: 'vis_div'
});
wrapper.draw();
- Chart의 interaction은 ready, select, error, onmouseover/onmouseout 이 존재한다 (참조)
- addListener를 이용하여 event handler를 등록한다
function selectHandler() {
var selectedItem = chart.getSelection()[0];
if (selectedItem) {
var topping = data.getValue(selectedItem.row, 0);
alert('The user selected ' + topping);
}
}
google.visualization.events.addListener(chart, 'select', selectHandler);
- Formatters 에는 arrow, bar, color, date, number 등이 있고, 주로 table에서 사용된다. 단, color는 차트에서도 사용됨 (참조)
- Advanced Chart 기능을 쓰고 싶을 경우 (참조)
기존 소스 이해
- google-chart에 대한 directive 소스에 resize 넣음
/**
* @description Google Chart Api Directive Module for AngularJS
* @version 0.0.9
* @author Nicolas Bouillon <nicolas@bouil.org>
* @author GitHub contributors
* @moidifier Peter Yun <nulpulum@gmail.com>
* @license MIT
* @year 2013
*/
(function (document, window, angular) {
'use strict';
angular.module('googlechart', [])
.constant('googleChartApiConfig', {
version: '1.0',
optionalSettings: {
packages: ['corechart']
}
})
/**
* 인코딩 주의)
* index.html 안에 UTF-8 설정
* <meta http-equiv="content-type" content="text/html; charset=UTF-8">
*
* 사용예)
* <script type="text/javascript" src="https://www.google.com/jsapi"></script>
* <script type="text/javascript">
* google.load('visualization', '1.0', {'packages':['corechart']});
* google.setOnLoadCallback(drawChart);
* function drawChart() { ... }
* </script>
*
* @변경 : jsapi.js 파일을 로컬에서 읽어오도록 수정할 수 있다
* googleJsapiUrlProvider.setProtocol(undiefined);
* googleJsapiUrlProvider.setUrl('jsapi.js');
*
* @참조 : https://google-developers.appspot.com/chart/interactive/docs/quick_start
*/
.provider('googleJsapiUrl', function () {
var protocol = 'https:';
var url = '//www.google.com/jsapi';
this.setProtocol = function(newProtocol) {
protocol = newProtocol;
};
this.setUrl = function(newUrl) {
url = newUrl;
};
this.$get = function() {
return (protocol ? protocol : '') + url;
};
})
/**
* google.load('visualization', '1.0', {'packages':['corechart']}); 수행하는 팩토리
*
* @param {[type]} $rootScope [description]
* @param {[type]} $q [description]
* @param {[type]} apiConfig [description]
* @param {[type]} googleJsapiUrl [description]
* @return {[type]} [description]
*/
.factory('googleChartApiPromise', ['$rootScope', '$q', 'googleChartApiConfig', 'googleJsapiUrl', function ($rootScope, $q, apiConfig, googleJsapiUrl) {
var apiReady = $q.defer();
var onLoad = function () {
// override callback function
var settings = {
callback: function () {
var oldCb = apiConfig.optionalSettings.callback;
$rootScope.$apply(function () {
apiReady.resolve();
});
if (angular.isFunction(oldCb)) {
oldCb.call(this);
}
}
};
settings = angular.extend({}, apiConfig.optionalSettings, settings);
window.google.load('visualization', apiConfig.version, settings);
};
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.src = googleJsapiUrl;
if (script.addEventListener) { // Standard browsers (including IE9+)
//console.log('>>> 1. load jsapi...');
script.addEventListener('load', onLoad, false);
} else { // IE8 and below
//console.log('>>> 2. load jsapi...');
script.onreadystatechange = function () {
if (script.readyState === 'loaded' || script.readyState === 'complete') {
script.onreadystatechange = null;
onLoad();
}
};
}
head.appendChild(script);
return apiReady.promise;
}])
/**
* Element 또는 Attribute로 사용할 수 있다. Element 사용시에는 IE8+에 대한 고려가 있어야 함
*
* 사용예)
* <div google-chart chart="chartObject" style="height:300px; width:100%;"></div>
*
* @param {[type]} $timeout [description]
* @param {[type]} $window [description]
* @param {[type]} $rootScope [description]
* @param {[type]} googleChartApiPromise [description]
* @return {[type]} [description]
*/
.directive('googleChart', ['$timeout', '$window', '$rootScope', 'googleChartApiPromise', function ($timeout, $window, $rootScope, googleChartApiPromise) {
return {
restrict: 'EA',
scope: {
chartOrig: '=chart',
onReady: '&',
select: '&'
},
link: function ($scope, $elm, attrs) {
// Watches, to refresh the chart when its data, title or dimensions change
$scope.$watch('chartOrig', function () {
$scope.chart = angular.copy($scope.chartOrig);
drawAsync();
}, true); // true is for deep object equality checking
// Redraw the chart if the window is resized
$rootScope.$on('resizeMsg', function () {
$timeout(function () {
// Not always defined yet in IE so check
if($scope.chartWrapper) {
drawAsync();
}
});
});
// Keeps old formatter configuration to compare against
$scope.oldChartFormatters = {};
function applyFormat(formatType, formatClass, dataTable) {
var i, cIdx;
if (typeof($scope.chart.formatters[formatType]) != 'undefined') {
if (!angular.equals($scope.chart.formatters[formatType], $scope.oldChartFormatters[formatType])) {
$scope.oldChartFormatters[formatType] = $scope.chart.formatters[formatType];
$scope.formatters[formatType] = [];
if (formatType === 'color') {
for (cIdx = 0; cIdx < $scope.chart.formatters[formatType].length; cIdx++) {
var colorFormat = new formatClass();
for (i = 0; i < $scope.chart.formatters[formatType][cIdx].formats.length; i++) {
var data = $scope.chart.formatters[formatType][cIdx].formats[i];
if (typeof(data.fromBgColor) != 'undefined' && typeof(data.toBgColor) != 'undefined')
colorFormat.addGradientRange(data.from, data.to, data.color, data.fromBgColor, data.toBgColor);
else
colorFormat.addRange(data.from, data.to, data.color, data.bgcolor);
}
$scope.formatters[formatType].push(colorFormat)
}
} else {
for (i = 0; i < $scope.chart.formatters[formatType].length; i++) {
$scope.formatters[formatType].push(new formatClass(
$scope.chart.formatters[formatType][i])
);
}
}
}
//apply formats to dataTable
for (i = 0; i < $scope.formatters[formatType].length; i++) {
if ($scope.chart.formatters[formatType][i].columnNum < dataTable.getNumberOfColumns())
$scope.formatters[formatType][i].format(dataTable, $scope.chart.formatters[formatType][i].columnNum);
}
//Many formatters require HTML tags to display special formatting
if (formatType === 'arrow' || formatType === 'bar' || formatType === 'color')
$scope.chart.options.allowHtml = true;
}
}
function draw() {
// 그리고 있는중에는 다시 그리기 안함
if (!draw.triggered && ($scope.chart != undefined)) {
draw.triggered = true;
// ref: https://docs.angularjs.org/api/ng/service/$timeout
$timeout(function () {
if (typeof($scope.formatters) === 'undefined')
$scope.formatters = {};
var dataTable;
if ($scope.chart.data instanceof google.visualization.DataTable)
dataTable = $scope.chart.data;
else if (angular.isArray($scope.chart.data))
dataTable = google.visualization.arrayToDataTable($scope.chart.data);
else
dataTable = new google.visualization.DataTable($scope.chart.data, 0.5);
if (typeof($scope.chart.formatters) != 'undefined') {
applyFormat("number", google.visualization.NumberFormat, dataTable);
applyFormat("arrow", google.visualization.ArrowFormat, dataTable);
applyFormat("date", google.visualization.DateFormat, dataTable);
applyFormat("bar", google.visualization.BarFormat, dataTable);
applyFormat("color", google.visualization.ColorFormat, dataTable);
}
var customFormatters = $scope.chart.customFormatters;
if (typeof(customFormatters) != 'undefined') {
for (var name in customFormatters) {
applyFormat(name, customFormatters[name], dataTable);
}
}
// resize > 0 이상이면 적용됨
// by peter yun
// <div style="height:100%" id="gc">
// <div google-chart chart="chartObject" resize="#gc" style="width:100%;"></div>
// </div>
var height = angular.element(attrs.resize).height();
if(height) {
$scope.chart.options.height = height;
}
var chartWrapperArgs = {
chartType: $scope.chart.type,
dataTable: dataTable,
view: $scope.chart.view,
options: $scope.chart.options,
containerId: $elm[0]
};
$scope.chartWrapper = new google.visualization.ChartWrapper(chartWrapperArgs);
google.visualization.events.addListener($scope.chartWrapper, 'ready', function () {
$scope.chart.displayed = true;
$scope.$apply(function (scope) {
scope.onReady({chartWrapper: scope.chartWrapper});
});
});
google.visualization.events.addListener($scope.chartWrapper, 'error', function (err) {
console.log("Chart not displayed due to error: " + err.message + ". Full error object follows.");
console.log(err);
});
google.visualization.events.addListener($scope.chartWrapper, 'select', function () {
var selectedItem = $scope.chartWrapper.getChart().getSelection()[0];
$scope.$apply(function () {
$scope.select({selectedItem: selectedItem});
});
});
$timeout(function () {
$elm.empty();
$scope.chartWrapper.draw();
draw.triggered = false;
});
}, 0, true);
}
}
/**
* jsapi의 load가 끝나면 draw하도록 promise를 걸어 놓은 것
*
* @return {[type]} [description]
*/
function drawAsync() {
googleChartApiPromise.then(function () {
draw();
})
}
}
};
}])
/**
* window즉 브라우져의 사이즈가 변경되면 google chart의 size를 변경토록 한다. 단 html설정에서 다음과 같이 되어야 한다
* <div google-chart chart="chartObject" style="height:300px; width:100%;"></div>
* height는 %가 아니라 자신이 속한 parent dive의 height를 따르도록 해주어야 한다
*
* @param {[type]} $rootScope [description]
* @param {[type]} $window [description]
* @return {[type]} [description]
*/
.run(['$rootScope', '$window', function ($rootScope, $window) {
angular.element($window).bind('resize', function () {
$rootScope.$emit('resizeMsg');
});
}]);
})(document, window, window.angular);
<참조>
'AngularJS > Concept' 카테고리의 다른 글
[AngularJS] 배우는 방법 & 기본 개념 잡기 (2) | 2014.08.03 |
---|---|
[Directive Driven] 하이브리드 앱 프레임워크 for Angular.js (0) | 2014.06.17 |
[AngularJS] Prototypal 상속과 Isolated Scope의 관계에 대하여 (0) | 2013.11.28 |
[SPA] Modern Web에서의 Design Pattern 인식의 전환 (0) | 2013.09.28 |
[AngularJS] Service 종류 이해하기 (0) | 2013.08.22 |