Reputation: 2701
I have a simple form, where user selects date from and date to, where afterwards the tool automatically retrieves data from a website (it returns a JSON).
Here is how my angular controller looks like:
(function () {
angular.module("app-machines", ['ngFlatDatepicker'])
.factory('MachinesService', ['$http', MachinesService])
.controller('mainController', ['$scope', 'MachinesService', mainController]);
function mainController($scope, MachinesService) {
$scope.datepickerConfig_From = {
allowFuture: true,
dateFormat: 'DD.MM.YYYY',
minDate: moment.utc('2015-09-13'),
maxDate: moment.utc('2015-09-17')
};
$scope.datepickerConfig_To = {
allowFuture: true,
dateFormat: 'DD.MM.YYYY',
minDate: moment.utc('2015-09-13'),
maxDate: moment.utc('2015-09-17')
};
$scope.date_from = "14.09.2015";
$scope.date_to = "15.09.2015";
$scope.machines = [];
$scope.errorMessage = "";
$scope.change = function () {
MachinesService.getMachines($scope.date_from, $scope.date_to).then(function (response) {
angular.copy(response.data, $scope.machines);
}, function (error) {
$scope.errorMessage = "Failed to load data:" + error;
});
};
$scope.change();
}
Where in my getMachines
I am calling a simple GET request which looks more or less like this (example):
return $http.get("/api/machine/2015-09-14_2015-09-16");
The returned JSON is a array of objects with the following structure (just informational)
I can retrieve the data without a problem now (with a fantastic help from you folks). I am now trying to display a chart for each of my returned machines. This means that on my page I am trying to do something like this:
<div class="col-md-12" ng-repeat="machine in machines">
<h1> {{ machine.name }}</h1>
<div class="col-md-6" ng-repeat="category in machine.categories">
<h3> {{ category.name }}</h3>
<div class="col-md-6" ng-repeat="day in category.days">
<p>{{day.date | date : 'dd.MM' }}</p>
</div>
</div>
</div>
Here (just simple example) I am looping through the machines and I am displaying category with days. Instead of displaying categoreis (with the days) I simply would like to insert a bar chart with the data.
I have found ChartJs which allows me to do that. Here is my example script which displays a chart on my page:
var data = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "My First dataset",
// The properties below allow an array to be specified to change the value of the item at the given index
// String or array - the bar color
backgroundColor: "rgba(100,220,220,0.2)",
// String or array - bar stroke color
borderColor: "rgba(220,220,220,1)",
// Number or array - bar border width
borderWidth: 1,
// String or array - fill color when hovered
hoverBackgroundColor: "rgba(220,220,220,0.2)",
// String or array - border color when hovered
hoverBorderColor: "rgba(220,220,220,1)",
// The actual data
data: [65, 59, 80, 81, 56, 55, 40],
// String - If specified, binds the dataset to a certain y-axis. If not specified, the first y-axis is used.
yAxisID: "y-axis-0",
},
{
label: "My Second dataset",
backgroundColor: "rgba(220,220,220,0.2)",
borderColor: "rgba(220,220,220,1)",
borderWidth: 1,
hoverBackgroundColor: "rgba(220,220,220,0.2)",
hoverBorderColor: "rgba(220,220,220,1)",
data: [28, 48, 40, 19, 86, 27, 90]
}
]
};
var options = {
scales: {
xAxes: [{
stacked: true
}],
yAxes: [{
stacked: true
}]
}
};
var ctx = document.getElementById("myChart");
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options
});
This works like a charm, however only for one chart - because we are targeting the context with document.getElementById("myChart")
.
The question is - how can I change this to create a chart from my returned data? As a backup solution I came up with pre-designing the page in advance (I know maximum number of machines returned) and simply hide the ones that should not appear....but I know that is not the right approach (it's a back-up plan). I would like to learn how to do it properly.
Any help in this matter would be more than appreciated. I am a AngularJS newbie, therefore your code samples would be more than welcome!
EDIT:
As suggested I have updated my HTML code to the following:
<div class="col-md-12" ng-repeat="machine in machines">
<h1> {{ machine.name }}</h1>
<canvas id="{{'myChart_' + $index}}" width="400" height="400"></canvas>
</div>
which names those charts without a problem. Then, under my controller I have changed the code to the following:
$scope.change = function () {
MachinesService.getMachines($scope.date_from, $scope.date_to).then(function (response) {
//$scope.machines = response.data;
angular.copy(response.data, $scope.machines);
}, function (error) {
$scope.errorMessage = "Failed to load data:" + error;
}).finally(function () {
var data = {..same as above};
var options = {...same as above};
//now assign those values to the representative charts
for (var i = 0; i < $scope.machines.length -1; i++) {
var ctx = document.getElementById("myChart_" + i);
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options
});
}
});
};
The problem I have encountered here is, that my charts are rendered after my code executes. This means that I try to find my charts before they are actually created by Angular on my page. I have tried (as you can see) to add .finally
clause to my code, but it did not work.
Is there a switch / code that I need to use in order for this solution to work?
EDIT2
I have also tried to add the following parameter $timeout
to my controller as following:
.controller('mainController', ['$scope', 'MachinesService', '$timeout', mainController]);
then I made the finally clause an external function like this (inside same controller):
var changeValues = function () {
var data = {...same as before};
var options = {...same as before};
for (var i = 0; i < $scope.machines.length - 1; i++) {
var ctx = document.getElementById("myChart_" + i);
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options
});
}
};
and from within the finally clause I called my function like this $timeout(changeValues, 0);
but it still does not work. I am quite lost right now. What am I missing?
FINAL:
Here is how I had to edit my code in the end:
angular.module("app-machines", ['ngFlatDatepicker'])
.factory('MachinesService', ['$http', MachinesService])
.controller('mainController', ['$scope', 'MachinesService', '$timeout', mainController])
.directive('onFinishRender', function ($timeout)
{
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
});
}
}
}
});
Upvotes: 3
Views: 2546
Reputation: 1201
There might be a better answer, but..
You could use an angular loop to create the initial HTML elements.
<div class="col-md-12" ng-repeat="machine in machines">
<h1> {{ machine.name }}</h1>
<canvas id="{{'myChart_' + $index}}" width="400" height="400"></canvas>
</div>
Then in your controller pass the elements to the DOM and $broadcast
an event to draw the charts.
$scope.change = function () {
MachinesService.getMachines($scope.date_from, $scope.date_to).then(function (response) {
angular.copy(response.data, $scope.machines);
$scope.$broadcast('chartReady'); //broadcast the event
}, function (error) {
$scope.errorMessage = "Failed to load data:" + error;
});
};
Also in your controller handle the broadcasted event. I borrowed and modified this code from here.
directive('drawCharts', ['$timeout', function ($timeout) {
return {
link: function ($scope, element, attrs) {
$scope.$on('chartReady', function () {
$timeout(function () { // You might need this timeout to be sure its run after DOM render.
//get chart elements and draw chart here
for (var i = 0; i < $scope.machines.length -1; i++) {
var ctx = document.getElementById("myChart_" + i);
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options
});
}
}, 0, false);
})
}
};
}]);
Upvotes: 2