Generate a dynamic chart using Angular-chart.js on a canvas that is created on the fly

I am facing an issue with displaying a chart or table in a div based on an XHR response. In the case of a chart, I want to replace the div contents with a canvas element that will be used by chart.js to show a graph.

When I directly include the canvas element in the HTML code, angular-chart.js successfully renders the graph. However, if I dynamically inject the canvas into the div using JavaScript, although the canvas element is present in the DOM, the chart does not display.

How can I resolve this problem?

HTML

<div ng-controller="ChartCtrl">
  <div>
    {{chart.name}}
    This works (doughnut):
    <canvas id="chart-{{$index}}" class="chart chart-doughnut" chart-data="chart.data" chart-labels="chart.labels"></canvas>
  </div>
  This Doesn't work ({{chart.type}}):
  <div id="chartDiv"> </div>
</div>

Javascript

var myApp = angular.module('myApp', ['chart.js']);

// Create the controller, the 'ToddlerCtrl' parameter 
// must match an ng-controller directive
myApp.controller('ChartCtrl', function ($scope) {
   var chart_div = $('#chartDiv');
   chart_div.empty();
   canvas_html = '<canvas id="chart-2" class="chart chart-doughnut" chart-data="chart.data" chart-labels="chart.labels"></canvas>';
   console.log(canvas_html)
   chart_div.append(canvas_html);
   //console.log(chart_div)
  $scope.chart =    {
     name: 'Chart 1',
     type: 'Doughnut',
     labels: ['EVSC', 'ISB'],
     data: [13, 44]
   };
});

Plunker Example: http://plnkr.co/edit/9JkANojIl8EXRj3hJNVH?p=preview

Answer №1

To modify the structure of the DOM, you cannot simply append your newly created element using traditional "vanilla" or "jQuery" methods.

Instead, you must compile your HTML string or DOM element to generate a template function that will be associated with the scope. This process involves traversing the DOM tree and matching DOM elements with relevant directives.

// Initialize the application, ensuring 'myApp' matches ng-app
var myApp = angular.module('myApp', ['chart.js']);

// Define the controller, naming it 'ChartCtrl' as per the ng-controller directive
myApp.controller('ChartCtrl', function ($scope, $compile) {
  canvas_html = '<canvas id="chart-2" class="chart chart-doughnut" chart-data="chart.data" chart-labels="chart.labels"></canvas>';

  var element = angular.element(canvas_html);
  $compile(element)($scope);
  angular.element('#chartDiv').append(element);

  $scope.chart =    {
     name: 'Chart 1',
     type: 'Doughnut',
     labels: ['EVSC', 'ISB'],
     data: [13, 44]
   };
});

You can view the outcome in this Plunker demo.

Answer №2

Wow, @HiDeo really nailed it with their answer! I have a more simplified Directive that achieves the same outcome. One key thing to note is the use of $compile. The chartData variable linked to the scope contains presets for Chartjs' charts, which can be customized as needed. An important addition is the $watch function on the update property to ensure smooth chart updates.

.directive('replaceWithChart',function($compile){
    return {
        restrict: 'A',
        scope: {
            chartData: '=replaceWithChart',
            height: '=',
            width: '='
        },
        compile: function(element){
            var canvasModel = angular.element('<canvas></canvas>');
            return {
                post: function postLink(scope,elem,attr){
                    elem.empty();
                    var canvas = canvasModel.clone();
                    elem.append($compile(canvas)(scope));

                    !scope.height && (scope.height = elem[0].style.height || (elem[0].offsetHeight || elem[0].clientHeight));
                    !scope.width && (scope.width = elem[0].style.width || (elem[0].offsetWidth || elem[0].clientWidth));

                    var chart = new Chart(canvas[0].getContext('2d'),scope.chartData);
                    canvas.attr('height',(canvas[0].style.height = scope.height + 'px'));
                    canvas.attr('width',(canvas[0].style.width = scope.width + 'px'));

                    scope.$watch('chartData.update',function(){
                        scope.chartData.update && !(scope.chartData.update = false) && chart.update();
                    });
                    elem.on('$destroy',chart.destroy.bind(chart));
                    // Fixed some rendering issue, but I can't remember which
                    window.setTimeout(function(){
                        var padding = elem[0].style.padding || (window.getComputedStyle(elem[0],null).getPropertyValue('padding'));
                        elem[0].style.padding = '0';
                        elem[0].style.padding = padding;
                        chart.resize();
                    },300); // Magic number - I found this worked best
                }
            };
        }
    };
})

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

Nested Tab Generation on the Fly

My goal is to create dynamically nested tabs based on my data set. While I have successfully achieved the parent tabs, I am encountering an issue with the child tabs. Code $(document).ready(function() { var data1 = [["FINANCE"],["SALE"],["SALE3"]]; var da ...

Using THREE.js: Finding the nearest point between two rays in a THREE.js environment

In my current project with THREE.js, I am working with two THREE.Ray objects. Both rays have an origin point represented by a Vector3 and a direction indicated by another Vector3. I am currently exploring how to determine the nearest intersection point be ...

Getting JSON data from an Angular JS controller can be achieved by utilizing the built-in

My user login function includes a method called logincheck, which takes in parameters and sends a request to the server. Upon success, it redirects the user to the dashboard with the member ID. this.logincheck = function(log) { var pa ...

What are the methods for incorporating reflection into three.js?

While working on a WebGL project using Three.js, I am aiming to incorporate a reflective cube surface that mimics the appearance of a mobile phone display. The surface should be able to reflect light while maintaining a black color scheme. ...

Steps for adding information to an AngularJS scope

I am facing an issue with setting the data inside the scope once it becomes active. On my HTML page, I have a show/hide menu that displays data when a button is clicked. I need to store this data within the scope correctly. Please advise on any corrections ...

Verify in Jquery whether a table row contains no elements with a specified class before removing any duplicates

I have an HTML table: <div class="1233">hello</div> <table cellpadding="0" cellspasing="0" class="sortable zebra tablesorter tablesorter-default" id="articles-table"> <thead> <tr class="tablesorter-headerRow"> ...

Can you explain the purpose of the statement `var MyConstructor = function MyConstructor()`?

Can you explain the distinction between these two code snippets: var NodestrapGenerator = module.exports = function NodestrapGenerator() { yeoman.generators.Base.apply(this, arguments); // more code here }; and: var NodestrapGenerator = module.expor ...

Issue with Cloud Code function preventing data from being saved

After successfully testing this code in Angular and getting the correct responses in console.log, I decided to migrate it to cloud code. Since the function manipulates data in the user table, I had to use the master key and implement it in cloud code. Howe ...

Converting a base64 string to a PNG format for uploading to an S3 bucket from the frontend

Feeling a bit overwhelmed here, hoping this isn't just a repeat issue. I've come across similar problems but none of the solutions seem to be working for me at the moment. I'm currently utilizing react-signature-canvas for allowing users to ...

The RC-dock library's 'DockLayout' is not compatible with JSX components. The instance type 'DockLayout' is not a valid JSX element and cannot be used as such

Despite encountering similar questions, none of the provided answers seem to address the issue within my codebase. My project utilizes React 17, Mui v5, and TS v4. I attempted to integrate a basic component from an external package called rc-dock. I simply ...

Setting up Quasar plugins without using Quasar CLI: A step-by-step guide

I integrated Quasar into my existing Vue CLI project by running vue add quasar. Now I'm attempting to utilize the Loading plugin, but unfortunately, it's not functioning as expected. Here is the configuration setup for Quasar/Vue that I have in ...

Learn the steps to retrieve a user's profile picture using the Microsoft Graph API and showcase it in a React application

I'm currently working on accessing the user's profile picture through Microsoft's Graph API. The code snippet below demonstrates how I am trying to obtain the profile image: export async function fetchProfilePhoto() { const accessToken = a ...

Incorporate various Vue.js components into the main parent component

I am currently utilizing Vue.js to create a user interface for my HTML5 game. I have a scenario where I want to define UI containers that essentially group other UI components and position them on the screen. Here's an example of what I'd like to ...

What is preventing Node.js from executing my JavaScript code in the terminal?

I need help understanding why my JavaScript code isn't working properly. Can someone explain the Uncaught SyntaxError: Unexpected Identifier error message? ...

What strategies can be implemented to maximize the effectiveness of Office ribbon commands within an AngularJS application?

Currently, I have developed an Office add-in using AngularJS (version 1.4) and ASP.NET MVC 4.5. The Angular controller and service JS files contain a significant amount of functionality that has already been implemented. Lately, I have been exploring the ...

Using the ControllerAs syntax in conjunction with $scope methods

Currently working on incorporating the controllerAs syntax into AngularJS 1.3 Here is how I'm starting my function declarations: function() { var myCtrl = this; myCtrl.foo = foo; // Successfully implemented myCtrl.$on("foo", bar); // Enc ...

Fetching database entries upon page load instead of using the keyup function in JavaScript

Here is an HTML form input provided: <input type="text" id="username" value=""> In this scenario, when a username like "John" is entered and the enter button is pressed, the script below retrieves database records: $(function(){ //var socket = ...

Navigating Vue 3's slot attributes: A step-by-step guide

Currently, I am facing an issue with a Vue component named MediaVisual. This component contains a slot. The problem arises when attempting to retrieve the src attribute of the slot element that is passed in. Surprisingly, this.$slots.default shows up as u ...

React and Express are two powerful technologies that work seamlessly

Here lies the contents of the mighty index.html file <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdnjs.cloudflare/ajax/libs/babel-core/5.8.23/browser.min.js"></script> <s ...

Unable to execute script tag on PHP page loading

When I make an AJAX request to fetch JavaScript wrapped in <script> tags that needs to be inserted on the current page, how can I ensure that the code executes upon insertion? Here's the snippet I'm using to add the code: function display ...