Reputation: 1231
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:
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
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
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
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