Generating ChartJS in Real-time

I have a straightforward form where the user selects a start date and an end date. Once these dates are selected, the tool automatically fetches data from a website in JSON format.

Below is the code for my Angular controller:

(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();
    }

In my getMachines function, I make a simple GET request like this example:

return $http.get("/api/machine/2015-09-14_2015-09-16");

The JSON returned is an array of objects with the following structure for reference:

  • Machine Name
    • Categories (under specific machine)
      • Days (each category contains a collection of days with data)

I can fetch the data successfully now thanks to your help. My next step is to display a chart for each returned machine on the page. The HTML snippet below demonstrates how I'm attempting to do it:

        <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>

Instead of displaying categories with days, I aim to insert a bar chart with the data. To achieve this, I found ChartJs which allows me to create such charts. Below is an example script displaying a chart on the page:

var data = {..chart data};
var options = {...chart options};

var ctx = document.getElementById("myChart");


var myBarChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: options
});

This works well for one chart since it targets the context specified with

document.getElementById("myChart")
. The problem arises when trying to dynamically create multiple charts based on the fetched data. How can I modify my code to create individual charts for each machine?

Any guidance or code samples would be greatly appreciated as I am relatively new to AngularJS!

EDIT:

To address this issue, I updated my HTML code as follows:

        <div class="col-md-12" ng-repeat="machine in machines">
            <h1> {{ machine.name }}</h1>
            <canvas id="{{'myChart_' + $index}}" width="400" height="400"></canvas>

        </div>

This adjustment allowed me to name the charts accordingly. In my controller, I made the following changes:

    $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};

            //assign values to the respective 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 challenge I faced here was that the charts were being rendered after the execution of my code. It seemed like I was trying to access the charts before they were created by Angular on the page. I attempted to use .finally but it did not resolve the issue.

Is there a specific technique or code snippet that I should implement for this solution to work?

EDIT2

I also tried adding the $timeout parameter to the controller like this:

.controller('mainController', ['$scope', 'MachinesService', '$timeout', mainController]);

I then defined an external function within the controller as follows:

    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
            });
        }
    };

Within the .finally clause, I called the function using $timeout(changeValues, 0); but the issue persisted. I seem to be missing something crucial. Any insights into what might be causing this problem?

FINAL:

Here's the final edit to my code:

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');
                    });
                }
            }
        }
    });

Answer №1

Perhaps there is a more optimal solution, however..

You can utilize an angular loop to generate 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>

Next, in your controller, pass the elements to the DOM and trigger an event using $broadcast 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;
    });
};

Additionally, in your controller, manage the broadcasted event. I've adapted and adjusted 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 ensure it runs after the DOM renders.
                  //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);
            })
        }
    };
}]);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

VueJS functions properly on Google Chrome, however it may encounter compatibility issues when using

I am currently working on a VueJs app resembling an auction, with the backend powered by Laravel. Everything runs smoothly when I test it on Chrome, but for some reason, Safari seems to be giving me trouble. The app consists of two main components: Deale ...

Arrange a collection of words in alphabetical order based on word importance

Given the array below [ { name: '4K UHD', commentator: 'Ali' }, { name: 'English 1 HD', commentator: 'Ahmed' }, { name: 'English 3 HD', commentator: 'Ahmed' }, { name: 'Premium 1 HD&a ...

AngularJS Custom Navigation based on User Roles

I am currently developing a small web application using angular. My goal is to implement role-based navigation in the app. However, I am facing an issue where the isAdmin function does not seem to be getting called on page load, resulting in only the foo a ...

Steps to Obtain a JSON File in UTF-8 Format

Using LitJSON library has resulted in some unexpected behavior. Is there a JSON library that preserves accents during conversion? Below is a sample test: test.json [{"id":"CS_001","name":"L'élément","type":"Tôt"},{"id":"CS_002","name":"L'o ...

The issue with Ajax file upload is that it only processes the first file in the filelist array for

I am struggling with an issue while using jquery and materialize for asynchronous file upload and form submit. The code seems to work fine when I use get(0).files[0], but it only returns the first file at index [0]. However, when I attempt to loop through ...

What could be causing my external JavaScript file to not function properly upon loading my HTML file?

I have organized my code by separating the HTML and JavaScript portions. The JavaScript code is now in its own separate file and I use 'script' tags to reference it in the HTML file. Within the JavaScript code, I have two functions - one creates ...

Ways to extract meaningful data from json

I am working with some JSON data: {"response":{"status":"SUCCESS","totalsent":1,"cost":2}} My goal is to retrieve the value corresponding to the key "status". Can anyone suggest how I can achieve this usin ...

When the condition within the click function is met, concatenate the variable

I'm currently working on a function that involves adding "http://" to the variable 'bar' if it's not already included. This modified 'bar' value will then be sent via ajax to be inserted into the database and also displayed ba ...

Having issues with $emitting not working for parent-child components in Vue. Any ideas on what I might be doing incorrectly?

I have a login component that I need to call in the main vue component of App.vue. Within the login vue, when I click on any button, it should activate another vue component using Vue.js router to replace the login page. I have searched for solutions but h ...

What is the reason that property spreading is effective within Grid components but not in FormControl components?

Explore the sandbox environment here: https://codesandbox.io/s/agitated-ardinghelli-fnoj15?file=/src/temp4.tsx:0-1206. import { FormControl, FormControlProps, Grid, GridProps } from "@mui/material"; interface ICustomControlProps { gridProps?: ...

Determining the moment a user exits a page on Next JS

Is there a way to track when the user exits a Next JS page? I have identified 3 possible ways in which a user might leave a page: Clicking on a link Performing an action that triggers router.back, router.push, etc... Closing the tab (i.e. when beforeunloa ...

What is the best way to invoke a function within an AngularJS controller?

Currently, I am exploring the most efficient method of calling a function from an AngularJS controller externally. In our setup, data is transmitted from a Python backend to the frontend using JavaScript functions. To feed this data into the Angular contr ...

Generate a JSON file in Python using a for loop and store a variable in the JSON structure

I'm fairly new to working with Json files. I would like to create a Json file containing 10 JSON objects, each representing data from a sensor such as temperature, flow, and pressure. The values for each parameter are currently stored in variables. Ho ...

Ways to choose a designated element without relying on ids or classes

I am looking to create an on-click function for each button, so I require a method to select each one individually without relying on IDs <div class="col"> <div> <b>Name:</b> <span ...

Is it universally compatible to incorporate custom attributes through jquery metadata plugin in all web browsers?

Picture this: a fictional markup that showcases a collection of books with unique attributes, utilizing the metadata plugin from here. <div> Haruki Murakami </div> <div> <ul> <li><span id="book5" data="{year: 2011} ...

"Optimize your website by incorporating lazy loading for images with IntersectionObserver for enhanced performance

How can I use the Intersection Observer to load an H2 tag only when the image is visible on the page? Here is the JavaScript code I currently have: const images = document.querySelectorAll('img[data-src]'); const observer = new IntersectionObser ...

React Material UI DataGrid: Error encountered - Unable to access property 'useRef' due to being undefined

The challenge at hand Currently, I am faced with a dilemma while attempting to utilize the React DataGrid. An error in the form of a TypeError: Cannot read property 'useRef' of undefined is appearing in my browser's stack trace. https://i.s ...

Rails does not transfer data-attributes in HTML5

I have a layout set up to show users: %table.table %tbody - @users.each do |user| %tr %td= avatar_tag user, {small:true, rounded:true} %td = username user .online-tag = user.online? %td= ...

Using JavaScript to listen for events on all dynamically created li elements

Recently, I've created a simple script that dynamically adds "li" elements to a "ul" and assigns them a specific class. However, I now want to modify the class of an "li" item when a click event occurs. Here's the HTML structure: <form class ...

Angular - Detecting Scroll Events on Page Scrolling Only

I am currently working on implementing a "show more" feature and need to monitor the scroll event for this purpose. The code I am using is: window.addEventListener('scroll', this.scroll, true); Here is the scroll function: scroll = (event: any) ...