I started learning Angular Js and D3 Js this week. The code below may be pathetic but please bear with me.
Problem: I am trying to consume a REST service which returns json {"chatjson":"[10,20,30]"}. I want to draw a pie chart using [10,20,30] received from REST.
Code OverView: I am consuming three REST service in ng-controller 'validateCtrl'. two services are running fine and showing desired data in angular but the from third REST (see function '$scope.getChartData' ) which returns {"chatjson":"[10,20,30]"} , which is JSON , which should show a pie chart based on the response at
donut-chart tag in angular code, which I am not getting.
The D3 Js code is written at the bottom section. D3 code works fine with hardcoded data.
Actual code
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
<html>
<head>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<h2>Angular Example</h2>
<form ng-app="myApp" ng-controller="validateCtrl"
name="myForm" novalidate>
<p>Username:<br>
<input type="text" name="user" ng-model="user" required>
<span style="color:red" ng-show="myForm.user.$dirty && myForm.user.$invalid">
<span ng-show="myForm.user.$error.required">Username is required.</span>
</span>
</p>
<p>country:<br>
<input type="text" name="country" ng-model="country" required>
<span style="color:red" ng-show="myForm.country.$dirty && myForm.country.$invalid">
<span ng-show="myForm.country.$error.required">country is required.</span>
</span>
</p>
<p>
<input type="submit" value="save" ng-click="send()"
ng-disabled="myForm.user.$dirty && myForm.user.$invalid ||
myForm.country.$dirty && myForm.country.$invalid">
</p>
<ul>
<li ng-repeat="y in detail">
{{ y.name + ', ' + y.country }}
</li>
</ul>
<input type="submit" value="Get Country List" ng-click="getData()">
<input type="submit" value="Get Pie Chart" ng-click="getChartData()">
<ul>
<li ng-repeat="x in names">
{{ x.name + ', ' + x.country }}
</li>
</ul>
<div>
<donut-chart data="chartData"></donut-chart>
</div>
</form>
<script>
var app = angular.module('myApp', []);
app.controller('validateCtrl', function($scope,$http) {
$scope.chartData=[12,13,60]; // Hard coded for testing purpose
$scope.user = 'Anukul';
$scope.country = 'India';
//=============== REST for fetching data from DB for Table details ==============
$scope.getData = function(){
var getApi="http://localhost:9080/nextapp/person/getList";
$http.get(getApi).success(function(response){$scope.names=response.records;
});
};
//=============== REST for fetching data for drawing chart ==============
$scope.getChartData = function(){
var getchartApi="http://localhost:9080/nextapp/person/getChart";
$http.get(getchartApi).success(function(response){$scope.chartData=response;
});
};
//=============== REST for inserting data in DB ==============
$scope.send = function(){
var name = $scope.user;
var country = $scope.country;
var api="http://localhost:9080/nextapp/person/"+name+"/"+country;
$http.get(api)
.success(function(response) {$scope.detail = response.result;});
};
//========== End of REST ==============================
});
//===================== new add for D3 Pie chart ============
app.directive('donutChart',function(){
function link(scope,el){
//---- D3 code goes here
var data = scope.data;
var color = d3.scale.category10()
var el = el[0]
var width = 500
var height = 500
var min = Math.min(width,height)
var pie = d3.layout.pie().sort(null)
var arc = d3.svg.arc()
.outerRadius(125)
.innerRadius(75)
var group = d3.select(el).append('svg')
.attr({width:width,height:height})
.append('g')
.attr('transform','translate('+width/2+','+height/2+')')
var arcs = group.selectAll(".arc")
.data(pie(data))
.enter()
.append('g')
.attr("class","arc")
arcs.append("path")
.attr("d",arc)
.attr("fill",function(d){return color(d.data);});
arcs.append("text")
.attr("transform",function(d){return "translate (" + arc.centroid(d)+")";})
.text(function(d){return d.data;});
//d3.select(el[0]).append('svg')
}
return{
link: link,
restrict:'E',
scope:{data:'='}
}
}
)
</script>
</body>
</html>
I think the main issue is that directives only run their link function once. The code posted in your question does all the drawing in the link function, which will only ever have access to the initial hard-coded data.
The solution, then, is to separate the drawing code from the link function in a form that you can call from a watcher:
app.directive('donutChart',function(){
function link(scope,element){
scope.el = element[0]; // Cache a reference to the element
draw(scope.data, scope.el); // Initial draw
}
function draw(data, el) {
var color = d3.scale.category10(),
width = 500,
height = 500,
min = Math.min(width,height),
pie = d3.layout.pie().sort(null),
arc = d3.svg.arc()
.outerRadius(125)
.innerRadius(75);
d3.select("svg").remove(); // Clear old pie chart
var group = d3.select(el).append('svg')
.attr({width:width,height:height})
.append('g')
.attr('transform','translate('+width/2+','+height/2+')'),
arcs = group.selectAll(".arc")
.data(pie(data))
.enter()
.append('g')
.attr("class","arc");
arcs.append("path")
.attr("d",arc)
.attr("fill",function(d){return color(d.data);});
arcs.append("text")
.attr("transform",function(d){return "translate (" + arc.centroid(d)+")";})
.text(function(d){return d.data;});
}
return{
link: link,
restrict:'E',
scope:{data:'='},
// Add a controller here to $watch for data changes
controller: function($scope) {
$scope.$watch('data', function(newData) {
draw(newData, $scope.el); // Re-draw the chart
});
}
};
});
Another thing I noticed might not be an actual issue because it depends on the actual server response. When setting the chart data, you do this:
$scope.chartData = response;
However, according to the text of your question, the expected response is like:
{ "chartjson": [10, 20, 30] }
If that's the case, and your test chart data is just an array, as seen here:
$scope.chartData = [12, 13, 60];
Then I would expect to get the array from the response like this:
$scope.chartData = response.chartjson;
You can see in this plunker that I mocked out $httpBackend
to return the example object from your question, and this appears to work.