블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2014. 6. 4. 00:20 AngularJS/Concept

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 기능을 쓰고 싶을 경우 (참조)

  - 웹툴상에서 차트의 API 테스트 하는 도구 



기존 소스 이해

  - 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);



<참조>

  - 구글 차트 Directive

  - 구글 차트 Directive 만들기 예제

  - 웹툴상에서 차트의 API 테스트 하는 도구 

  - Directive 수정 소스 

posted by 윤영식