2014/06/28 19:07

ElasticSearch(이하 ES) 엔진에 수집된 데이터에 대하여 Kibana 도움 없이 직접 Data Visualization 하는 기술 스택을 알아보고, 실데이터를 통한 화면 1본을 만들어 본다. 




ES 시각화를 위한 다양한 방식의 시도들


사전조사-1) 직접 클라이언트 모듈 제작하여 Data Visualization 수행 

  - elasticsearch.js 또는 elatic.js 사용하지 않고 REST 호출 통해 데이터 시각화 수행 

    (Protovis 는 D3.js 기반 Chart를 사용함)

  - FullScale.co에서는 dangle 이라는 시각화 차트를 소개

    (D3.js 기반의 AngularJS Directives 차트)

  - D3.js 기반의 전문적인 차트를 사용하는 방법을 익힐 수 있다. 하지만 제대로 갖춰진 ES 클라이언트 모듈의 필요성 대두


사전조사-2) elasticsearch.js 사용하여 Data Visualization 수행

  - elasticsearch.js와 D3.js, jquery, require.js를 통해서 샘플 데이터를 시각화 수행

    (AngularJS는 사용하지 않고, 전문적인 차트 모듈사용하지 않음)

  - AngularJS 기반으로 Protovis 또는 Dangle 차트를 사용하여 표현해 본다.


사전조사-3) elastic.js 사용하여 Data Visualization 수행

  - elastic.js 홈페이지에서 API를 숙지한다.

  - DSL API를 살펴보자. DSL을 이해하기 위해서는 ES의 Search API에 대한 이해가 필요하다.

  - Query를 작성하고 Filtering 하면 group by having과 유사한 facets (지금은 aggregation 을 사용)하여 검색을 한다.

    Query -> Filter -> Aggregation에 대해 알면 DSL구성을 이해할 수 있다.

  - 자신의 Twitter 데이터를 가지고 elastic.js와 angular.js를 사용하여 트윗 내용을 표현하는 방법 (GitHub 소스)




ES Data Visualization을 위한 나만의 Tech Stack 만들기 


  - ES 클라이언트 모듈 : elastic.js 의 DSL(Domain Specific Language)를 숙지한다. 

    elastic.js는 ElasticSearch의 공식 클라이언트 모듈인 elasticsearch.js 의 DSL 화 모듈로 namespace는 ejs 이다.  

  - 시각화를 위한 D3.js 의 개념 이해 (D3.js 배우기)

  - Kibana에서 사용하고 있는 Frontend MV* Framework인 AngularJS (AngularJS 배우기)

  - AngularJS 생태계 도구인 yeoman 을 통해 개발 시작하기 (generator-fullstack,  Yeoman 사용방법)

  - 물론 Node.js는 기본이다. 


그래서 다음과 같이 정리해 본다. 


  - AngularJS 기반 Frontend 개발

    1) Node.js 기초

    2) Yeoman + generator-angular 기반 

  - D3.js 기반의 Chart 이며 AngularJS 바로 적용가능토록 Directives 화 되어 있는 차트 중 선택사용

    1) Protovis

    2) Dangle

    3) Angular nvd3 charts (추천)

    4) Angular-Charts

    5) Angular Google Charts 

  - elasticsearch.js를 DSL로 만든 elastic.js 사용


그래서 다시 그림으로 정리해본 기술 스택



ES Data Visualization 개발을 위한 구성 stack 그림






== 이제 만들어 봅시다!!! ==


 

환경설정

  - node.js 및 yeoman 설치 : npm install -g generator-angular-fullstack 설치 (generator-angular는 오류발생)

  - twitter bootstrap RWD 메뉴화면 구성 (기본 화면 구성)

  - angular-ui의 angular-bootstrap 설치 :  bower install angular-bootstrap --save

  - elasticsearch.js 설치 : bower install elasticsearch --save

  - elastic.js 설치 : bower install elastic.js --save

  - angular-nvd3-directives chart 설치 : bower install angularjs-nvd3-directives --save



angular layout 구성 

  - 애플리케이션 생성 : GitHub Repository를 만들어서 clone 한 디렉토리에서 수행하였다

$ git clone https://github.com/elasticsearch-kr/es-data-visualization-hackerton 

$ cd es-data-visualization-hackerton 

$ yo angular-fullstack esvisualization

  - main.html 과 scripts/controllers/main.js 를 주로 수정함 

// main.html 안에 angular-nvd3.directives html 태그 및 속성 설정 

    <div class="row">

      <div class="col-xs-12 col-md-12">

        <div class="panel panel-default">

          <div class="panel-heading">

            <button type="button" ng-click="getImpression()" class="btn btn-default">Impression Histogram</button>

          </div>


          <div class="panel-body">


            <!-- angular-nvd3-directives : multi-bar chart -->

            <div class="col-xs-12 col-md-12">

              <nvd3-multi-bar-chart

                  data="impressionData"

                  id="dataId"

                  xAxisTickFormat="xAxisTickFormatFunction()"

                  width="550"

                  height="350"

                  showXAxis="true"

                  showYAxis="true">

                    <svg></svg>

              </nvd3-multi-bar-chart>

            </div>


          </div>

        </div>

      </div>

    </div>



// main.js 안에서 elasticsearch.js를 직접 호출하여 사용함 

angular.module('esvisualizationApp')

  .controller('MainCtrl', function ($scope, $http) {


    // x축의 값을 date으로 변환하여 찍어준다 

    $scope.xAxisTickFormatFunction = function(){

      return function(d){

        return d3.time.format('%x')(new Date(d));

      }

    }


    // 화면에서 클릭을 하면 impression index 값을 ES에서 호출하여 

    // ES aggregation json 결과값을 파싱하여 차트 데이터에 맵핑한다

    $scope.getImpression = function() {


      // ES 접속을 위한 클라이언트를 생성한다 

      var client = new elasticsearch.Client({

                                              host: '54.178.125.74:9200',

                                              sniffOnStart: true,

                                              sniffInterval: 60000,

                                            });


// search 조회를 수행한다. POST 방식으로 body에 실 search query를 넣어준다 

      client.search({

          index: 'impression',

          size: 5,

          body: {

            "filter": {

                "and": [

                    {

                      "range": {

                        "time": {

                            "from": "2013-7-1", 

                            "to": "2014-6-30"

                        }

                      }

                    }

                ]

            },

            "aggs": {

              "events": {

                "terms": {

                  "field": "event"   

                },

                "aggs" : {   

                  "time_histogram" : {

                      "date_histogram" : {

                          "field" : "time",

                          "interval" : "1d",   

                          "format" : "yyyy-MM-dd" 

                      }

                  }

                }

              }

            }


            // End query.

          }

      }).then(function (resp) {

         var impressions = resp.aggregations.events.buckets[0].time_histogram.buckets;

         console.log(impressions);


         var fixData = [];

         angular.forEach(impressions, function(impression, idx) {

          fixData[idx] = [impression.key, impression.doc_count];

         });

   

   // 결과에 대해서 promise 패턴으로 받아서 angular-nvd3-directives의 데이터 구조를 만들어 준다

   // {key, values}가 하나의 series가 되고 배열을 가지면 다중 series가 된다. 

         $scope.impressionData = [

            {

              "key": "Series 1",

              "values": fixData

            }

          ];


  // apply 적용을 해주어야 차트에 데이터가 바로 반영된다.

        $scope.$apply();

      });

    }


  });




결과 화면 및 해커톤 소감

  - "Impression Histogram" 버튼을 클릭하면 차트에 표현을 한다.

  - elastic.js의 DSL을 이용하면 다양한 파라미터에 대한 핸들링을 쉽게 할 수 있을 것같다. 

  - AngularJS 생태계와 elasticsearch.js(elastic.js)를 이용하면 Kibana의 도움 없이 자신이 원하는 화면을 쉽게 만들 수 있다. 

  - 관건은 역시 ES에 어떤 데이터를 어떤 형태로 넣느냐가 가장 먼저 고민을 해야하고, 이후 분석 query를 어떻게 짤 것인가 고민이 필요!



* 헤커톤 소스 위치 : https://github.com/elasticsearch-kr/es-data-visualization-hackerton



<참조> 

  - elasticsearch.js 공식 클라이언트 모듈을 DSL로 만든 elastic.js 

  - elastic.js를 사용한 ES Data Visualization을 AngularJS기반으로 개발

  - Protovis 차트를 이용한 facet 데이터 표현

  - ElasticSearch Data Visualization 방법

  - D3.js 와 Angular.js 기반으로 Data Visualization

  - ElasticSearch의 FacetAggregation 수행 : 향후 Facet은 없어지고 Aggregation으로 대체될 것이다.

  - AngularJS Directives Chart 비교

저작자 표시 비영리 변경 금지

'Mobicon Story > Data Driven' 카테고리의 다른 글

[Data Driven] ElasticSearch의 데이터 시각화 방법  (1) 2014/06/28
Posted by peter yun 윤영식
2014/06/17 14:33

이제 모든 서비스는 모바일 먼저 기획을 하고 웹 서비스로 넘어가야 한다고 생각한다. 즉, Contents -> UX -> Design -> Develop -> Launch로 넘어가면서 그 기본은 모바일이 먼저이다. 기존에 Bootstrap을 사용했을 경우 RWD (Response Web Design)이 적용되어 해상도에 따른 사용자 경험을 데스크탑과 모바일에 준하게 줄 수 있었지만 네이티브 앱과 유사한 UX 경험을 제공하기에는 부족하다. HTML5 기반으로 개발을 한다면 RWD 외에 Mobile First 전략을 가지고 있는 하이브리드 웹앱 Framework을 살펴 볼 필요가 있다. 살펴볼 두가지의-ionic, onsenui - 하이브리드 앱(모바일 웹앱) 프레임워크는 PhoneGap + Angular.js 와 결합되는 형태를 취하고 있다. mobile angular ui는 PhoneGap과 연결시키는 별도의 작업을 수반한다. 




                       




Ionic 하이브리드 웹앱 프레임워크

  - Ionic 홈페이지

  - PhoneGap에 최적화 되어 프로젝트를 생성해 준다.

  - https://github.com/driftyco/ionic-starter-sidemenu 처럼 ionic-starter-xx의 sidemenu, tabs, blank 3가지 타입을 제고하고 있다

// 설치 

$ sudo npm install -g cordova 

/usr/local/bin/cordova -> /usr/local/lib/node_modules/cordova/bin/cordova

cordova@3.5.0-0.2.4 /usr/local/lib/node_modules/cordova


$ sudo npm install -g ionic

/usr/local/bin/ionic -> /usr/local/lib/node_modules/ionic/bin/ionic

ionic@1.0.14 /usr/local/lib/node_modules/ionic


// 프로젝트 생성

$ ionic start myAppSideMenu sidemenu

Running start task...

Creating Ionic app in folder /Users/prototyping/myAppSideMenu based on sidemenu project

DOWNLOADING: https://github.com/driftyco/ionic-app-base/archive/master.zip

DOWNLOADING: https://github.com/driftyco/ionic-starter-sidemenu/archive/master.zip

Initializing cordova project.

Fetching plugin "org.apache.cordova.device" via plugin registry

Fetching plugin "org.apache.cordova.console" via plugin registry

Fetching plugin "https://github.com/driftyco/ionic-plugins-keyboard" via git clone


$ cd myAppSlideMenu


  - ionic 수행

$ ionic platform add android

Running platform task...

Adding platform android

.. 생략 ..

Project successfully created.

Installing "com.ionic.keyboard" for android

Installing "org.apache.cordova.console" for android

Installing "org.apache.cordova.device" for android


$ ionic build android

Running build task...

Building platform android

Running command: /Users/prototyping/myAppSideMenu/platforms/android/cordova/build

Buildfile: /Users/prototyping/myAppSideMenu/platforms/android/build.xml

... 중략 ...

-post-build:

     [move] Moving 1 file to /Usersprototyping/myAppSideMenu/platforms/android/ant-build

     [move] Moving 1 file to /Users/prototyping/myAppSideMenu/platforms/android/CordovaLib/ant-build

debug:

BUILD SUCCESSFUL

Total time: 48 seconds


// Android의 AVD를 먼저 실행해야 한다 

$ ionic emulate android

Running emulate task...

Emulating app on platform android

Running command: /Users/nulpulum/development/studygps_company/prototyping/myAppSideMenu/platforms/android/cordova/run --emulator

... 중략 ...

BUILD SUCCESSFUL

Total time: 9 seconds

Installing app on emulator...

Using apk: /Users/prototyping/myAppSideMenu/platforms/android/ant-build/HelloCordova-debug-unaligned.apk

Launching application...

LAUNCH SUCCESS


-- AVD 통해 본 모습

  

* 안드로이드 AVD 관리하기 (참조)


 - ionic 분석

 - ionic 명령을 통하여 ionic 프레임워크와의 통합 빌드작업을 자동 수행해 준다

// 완벽하게 angular.js 기반으로 운영된다 (angular.js v1.2.12 버전 번들링)

// www/lib/ionic/js/ionic.bundle.js 


// index.html 

<!DOCTYPE html>

<html>

  <head>

    <meta charset="utf-8">

    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">

    <title></title>


    <link href="lib/ionic/css/ionic.css" rel="stylesheet">

    <link href="css/style.css" rel="stylesheet">


    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above

    <link href="css/ionic.app.css" rel="stylesheet">

    -->


    <!-- ionic/angularjs js -->

    <script src="lib/ionic/js/ionic.bundle.js"></script>


    <!-- cordova script (this will be a 404 during development) -->

    <script src="cordova.js"></script>


    <!-- your app's js -->

    <script src="js/app.js"></script>

    <script src="js/controllers.js"></script>

  </head>


  <body ng-app="starter">

    <ion-nav-view></ion-nav-view>

  </body>

</html>

  - ionic components



Onsen UI 프레임워크 

  - PhoneGap을 설치와 Onsen ui 설치는 별개로 진행된다. 따라서 ionic과 같이 PhoneGap에 대한 기본 지식이 필요하다  

  - Onsen ui는 angular.js directive를 제공하는데 주력한다

  - PhoneGap 환경 설정 

// PhoneGap

$ npm install -g cordova

$ cordova create MyAppOnsenui && cd MyAppOnsenui

Creating a new cordova project with name "HelloCordova" and id "io.cordova.hellocordova" at location "/Users/prototyping/MyAppOnsenui"

Downloading cordova library for www...

Download complete


$ cordova platform add android

Creating android project...

Creating Cordova project for the Android platform:

Path: platforms/android

Package: io.cordova.hellocordova

Name: HelloCordova

Android target: android-19

Copying template files...

Running: android update project --subprojects --path "platforms/android" --target android-19 --library "CordovaLib"

Resolved location of library project to: /Users/prototyping/MyAppOnsenui/platforms/android/CordovaLib

Updated and renamed default.properties to project.properties

..중략..

Project successfully created.


-- build와 emulate 전에 하기의 Onsen ui 설치와 설정을 먼저 한 후에 수행한다 


$ cordova build android

.. 중략 ..

BUILD SUCCESSFUL

Total time: 32 seconds


-- android의 AVD를 먼저 실행해야 한다 

$ cordova emulate android


  - Onsenui components : 가장 기본적인 것들을 제공함 

  - Phonegap과 Angular.js 의 결합을 개발자가 직접해야함 

  - 활발한 Community feedback이 없는 상태이다 (참조)

  - Onsenui설치

// Onsen ui

$ cd MyAppOnsenui


$ bower init  명령으로 bower.json 파일을 만들거나 기존의 generator-angular-fullstack의 것을 사용한다

bower install onsenui --save

{

  "name": "mobiconsoft-mobile-app",

  "version": "0.1.0",

  "dependencies": {

    "angular": ">=1.2.*",

    "json3": "~3.3.1",

    "es5-shim": "~3.2.0",

    "angular-resource": ">=1.2.*",

    "angular-cookies": ">=1.2.*",

    "angular-sanitize": ">=1.2.*",

    "angular-ui-router": "~0.2.10",

    "restangular": "~1.4.0",

    "jquery": "1.11.1",

    "modernizr": "~2.8.1",

    "console-shim": "*",

    "jStorage": "~0.4.8",

    "angular-gettext": "~0.2.9",

    "ladda-studygps": "~0.9.4",

    "angular-ladda-studygps": "~0.1.7",

    "onsenui": "~1.0.4"

  },

  "devDependencies": {

    "angular-mocks": ">=1.2.*",

    "angular-scenario": ">=1.2.*"

  },

  "testPath": "test/client/spec"

}


-- http://onsenui.io/OnsenUI/project_templates/sliding_menu_navigator.zip 에서 샘플 파일을 다운로드 받아서 app 폴더에 있는 것을 www 폴더로 복사한다. 그리고 index.html안에 cordova.js를 넣는다. (개발시에는 404 NOT Found 발생함)


cordova build android

cordova emulate android

-- AVD로 띄워본 결과

 


  - Sliding Menu에 있어서는 Ionic 보다는 Onsenui가 좀 더 세련되어 보임 



Mobile Angular UI

  - Slide Menu 와 기본적인 모바일 화면 구성 컴포넌트를 angular.js 기반으로 Directive화 해놓았다. 

  - 친숙한 Bootstrap 기반의 UI를 제공한다. 그러나 Twitter bootstrap의 RWD기반은 아니다



ngCordova

    

  - cordova에서 제공하는 API를 angular 에서 사용할 수 있도록 Angular Service로 만들어 놓았음



좀 더 잘 정비된 것을 쓰고 싶다면 Ionic을 기초 기술부터 체크하면 갈 것이라면 Onsenui를 추천한다. 또한 좀 더 세련되 UI는 Onsenui에 점 수를 더 주고 싶다. 그리고 서로의 아키텍쳐에 대한 부분과 커스터마이징 여부는 좀 더 들여다 보아야 할 것으로 보인다. 각각의 상황과 특성에 따라 선택하여 사용하면 될 것으로 보인다. 



<참조>

  - Ionic 시작하기 

  - onsenui 시작하기

  - onsenui sliding menu zip 

sliding_menu_navigator.zip


  - mobile angular ui

  - iSO 8 UI Framework 7

  - ngCordova

  - 용어 정의 

    

저작자 표시 비영리 변경 금지
Posted by peter yun 윤영식
2014/06/10 16:34

지인이 프론트앤드 개발자를 구인하고 있어서 소개드립니다. 자세한 사항은 하기 내용 참조하세요




로앤컴퍼니와 함께 대한민국 법률시장의 대중화와 선진화를 만들어 갈 동료를 찾습니다. 함께 갑시다!


*채용안내: http://lawcompany.co.kr/jobs

*소개영상: http://youtu.be/5br8hf1ukRQ (by 무한도전 정준하)


<이런 분들과 함께 하고 싶습니다!>

- 법률과 IT의 융합을 통해 모든 국민에게 기여하는 서비스를 만들고 싶은 당신
- 미개척된 새로운 시장이 자신의 노력으로 변화하는 것을 경험하고 싶은 당신
- 서로를 자극하는 역량있는 사람들과 함께 재밌고 열정적으로 일을 하고 싶은 당신


<채용절차>

- 실력과 잠재력 중심 선발 (나이, 성별, 학력 무관)
- 이력서, 자기소개서, 포트폴리오 제출 (형태 제한 없음)
recruit@lawcompany.co.kr로 접수 후 1주일 이내에 회신해 드립니다.
- 2번의 면접을 통해 실력, 잠재력, 커뮤니케이션 능력, 팀과의 조화 여부 등을 확인합니다.


<참고사항>

- 출근은 오전 10시 (서초역 1분, 교대역 3분 거리)
- 당연하게도 월급은 섭섭지 않게, 제 때 드립니다.
- 서울에 연고가 없는 경우, 거주비 일부를 지원합니다.
- 고기를 자주 먹습니다. 야식은 더 자주 먹습니다.
- 치과 상담, 스케일링 치료 무료
- 가로수길 M모 카페, 모든 음료 하루 한잔 무료
- 변호사와 법률 상담 무료 및 법률 문제 해결 적극 지원





저작자 표시 비영리 변경 금지
Posted by peter yun 윤영식
2014/06/04 00:20

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 peter yun 윤영식
2014/06/03 16:25

MobiconSoft는 PC웹 환경을 Mobile로 옮기고, 기존 하드웨어와 Mobile을 접목한다는 Mobile + Convergence의 합성어 입니다. 3 Driven 기술을 기반으로 다양한 모바일 서비스를 런칭할 예정입니다. 개발 기술 스택은 MEAN Stack (MongoDB, Express.js, Angular.js, Node.js)를 사용하며, 데이터 분석 -사용자 분석 -을 통해 서비스 측정/개선을 수행하며 보안에 힘쏟을 것입니다. 


린 스타트업에서 MEAN Stack을 기반으로 개발하고, 3-Driven 에 대해 이야기를 함께 나누고 싶으시면 언제든 연락 주세요. 

Email : nulpulum@gmail.com





Directive Driven 

  - idea -> build -> product 영역

  - Angular.js를 기반으로 웹앱 개발 생산성 증대 

  - 모듈화를 통한 재배포 및 재사용성 증대 

  - Phonegap + SPA(Single Page Application) 의 하이브리드 웹앱 개발 생산성 증대 



Data Driven

  - measure -> data -> learn 영역

  - 초기 기술 스타트업이 간과하는 사용자 분석 기반 마련 

  - 사용자 분석을 위하여 ElasticSearchMongoDB, RedisMariaDB 등 저장소 활용



Security Driven

  - 웹상에 오고가는 내용에 대한 보안 강화

  - Node.js상에서의 인증과 권한 프레임워크 기반 마련

저작자 표시 비영리 변경 금지

'Mobicon Story' 카테고리의 다른 글

[MobiconSoft] 3 Driven Tech  (0) 2014/06/03
Posted by peter yun 윤영식

티스토리 툴바