Manjit Kumar
Manjit Kumar

Reputation: 1231

Reload angular directive to redraw morris.js charts after the API call

I'm using Morris charts with angular to show graphical reports in which the data comes from our backend server via an rest API call.

I'm able to see the retrieved data in console log but it's not displaying in charts. I found that the directive barchart is getting loaded before the api call and hence displaying data available in $scope.myModel.

I'm trying to find if there is some way in angular which can help me to reload the directive when data is received from api call. Could someone help me with this?

Bar Chart generated from code:

enter image description here

Here's my code

var sampleApp = angular.module('sample',[]);

sampleApp.directive('barchart', function() {

return {

    // required to make it work as an element
    restrict: 'E',
    template: '<div></div>',
    replace: true,
    // observe and manipulate the DOM
    link: function($scope, element, attrs) {

        var data = $scope[attrs.data],
            xkey = $scope[attrs.xkey],
            ykeys= $scope[attrs.ykeys],
            labels= $scope[attrs.labels];

        Morris.Bar({
                element: element,
                data: data,
                xkey: xkey,
                ykeys: ykeys,
                labels: labels
            });

    }

};

});

sampleApp.controller('sampleController',function($scope, $http){
$scope.values = []

$scope.xkey = 'range';

$scope.ykeys = ['total_tasks',     'total_overdue'];

$scope.labels = ['Total Tasks', 'Out of Budget Tasks'];
$http.get('http://api.*******.com/api/getAppID/?parameter=whatsapp').success( function(res) {
        if(!res.error) {
            if(res.status==1) res.status=true
            else res.status=false
    $scope.values[0] = res.metrices.total_shares
    $scope.values[1] = res.metrices.unique_share_count  
    $scope.values[2] = res.metrices.total_clicks
    $scope.values[3] = res.metrices.total_downloads
}
})
$scope.myModel = [
{ range: 'January', total_tasks: $scope.values[0], total_overdue: 5 },
{ range: 'January', total_tasks: $scope.values[1], total_overdue: 8 },
{ range: 'January', total_tasks: $scope.values[2], total_overdue: 1 },
{ range: 'January', total_tasks: $scope.values[3], total_overdue: 6 }
];

});

HTML PART:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="http://cdn.oesmith.co.uk/morris-0.4.3.min.css">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
    <script src="http://cdn.oesmith.co.uk/morris-0.4.3.min.js"></script>
    <script src="js/sample.js"></script>
    <meta charset=utf-8 />

</head>
<body ng-app="sample" ng-controller="sampleController">

    <barchart xkey="xkey" ykeys="ykeys" labels="labels" data="myModel"></barchart>


</body>

Upvotes: 2

Views: 6348

Answers (3)

Roy Calderon
Roy Calderon

Reputation: 6371

The only thing needed to "redraw" is to call $apply... Which will call $digest... which will check if any of the $watchers have(or not) changed, if so re painting/drawing the object.

setTimeout(function() {
  $scope.$apply(function (){
    morris = Morris.Area({
      element: element,
      data: angular.fromJson(val),
      xkey: $scope[attrs.xkey],
      ykeys: $scope[attrs.ykeys],
      labels: $scope[attrs.labels]
    });
  });
},1500);

Upvotes: 0

Juan Melo
Juan Melo

Reputation: 330

You don't need the 'flag' variable, you need to watch 'myModel' instead and you don't need to create a new Morris chart everytime model changes, second time model changes you just need to call the morris setData method.

I started my own directive starting from yours and I ended up with this code which works fine and redraws chart on window resize event, maybe someone could use it.

 (function () {
    'use strict';
    var module = angular.module('app.charts', []);
    module.directive('areachart', function ($window) {

        return {
            restrict: 'E',
            template: '<div></div>',
            replace: true,
            link: function ($scope, element, attrs) {
                var morris;
                angular.element($window).bind('resize', function () {
                    if (morris) {
                        console.log('morris resized');
                        morris.redraw();
                    }
                });

                attrs.$observe('value', function (val) {
                    if (!morris) {
                        console.log('creating chart');
                        morris = Morris.Area({
                            element: element,
                            data: angular.fromJson(val),
                            xkey: $scope[attrs.xkey],
                            ykeys: $scope[attrs.ykeys],
                            labels: $scope[attrs.labels]
                        });
                    } else {
                        console.log('setting chart values');
                        morris.setData(angular.fromJson(val));
                    }
                });
            }
        };
    });
}).call(this);

HTML

<areachart xkey="xkey" ykeys="ykeys" labels="labels" data-value="{{myModel}}"></areachart>

In your controller:

        $scope.xkey = 'y';

        $scope.ykeys = ['a', 'b'];

        $scope.labels = ['Series A', 'Series B'];

        $scope.myModel = [
                { y: '2006', a: 100, b: 90 },
                { y: '2007', a: 75, b: 65 },
                { y: '2008', a: 50, b: 40 },
                { y: '2009', a: 75, b: 65 },
                { y: '2010', a: 50, b: 40 },
                { y: '2011', a: 75, b: 65 },
                { y: '2012', a: 100, b: parseInt((Math.random() * 10000) / 10) }

Upvotes: 4

Manjit Kumar
Manjit Kumar

Reputation: 1231

I have tried really hard on this and managed to solve it. Posting this answer to help others fellow coders.

var sampleApp = angular.module('sample',[]);

sampleApp.directive('barchart', function() {

return {

    // required to make it work as an element
    restrict: 'E',
    template: '<div></div>',
    replace: true,
    // observe and manipulate the DOM
    link: function($scope, element, attrs) {
        $scope.$watch('flag', function() {

    $scope.myModel = [
       { range: 'January', total_tasks: $scope.values[0], total_overdue: 5 },
       { range: 'January', total_tasks: $scope.values[1], total_overdue: 8 },
       { range: 'January', total_tasks: $scope.values[2], total_overdue: 1 },
       { range: 'January', total_tasks: $scope.values[3], total_overdue: 6 }
     ];

    console.log($scope.flag + $scope.values+' The one we want watch')

    $scope.xkey = 'range';

    $scope.ykeys = ['total_tasks',     'total_overdue'];

    $scope.labels = ['Total Tasks', 'Out of Budget Tasks'];

        var data = $scope[attrs.data],
            xkey = $scope[attrs.xkey],
            ykeys= $scope[attrs.ykeys],
            labels= $scope[attrs.labels];

            var setData = function(){
            console.log('inside setData function');

        Morris.Bar({
                element: element,
                data: data,
                xkey: xkey,
                ykeys: ykeys,
                labels: labels
            });
            };
        if ($scope.flag == 1) {    
            attrs.$observe('data',setData)  
            }               
            });

    }

};

});


sampleApp.controller('sampleController',function($scope, $http){
$scope.flag = 0;
$scope.values = [];



$http.get('http://api.*******/api/*****/?appname=whatsapp').success( function(res) {
        if(!res.error) {
            if(res.status==1) res.status=true
            else res.status=false

    $scope.values[0] = res.metrices.total_shares
    $scope.values[1] = res.metrices.unique_share_count  
    $scope.values[2] = res.metrices.total_clicks
    $scope.values[3] = res.metrices.total_downloads
    $scope.flag = 1;
    console.log($scope.flag+"in api call"+$scope.values)

}

})


});

This worked for me. But if we remove that (flag==1) condition in $watch it redraws the chart 2 times with overlapping. I would appreciate if anyone can improve this answer.

This link was really helpful. [1]:http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest

Upvotes: 0

Related Questions