Create multiple dynamic stacked chart using chart.js in Angular 10?

2.9k Views Asked by At

I am trying to create multiple chart in angular but I am not sure the way I try to implement will is correct or not and I am unable to create multiple charts it replacing one with another

 <div *ngIf="chartData.length !== 0">
      <app-limus-utilisation-chart
      *ngFor="let entity of chartData" [chartdata]="entity"
      ></app-limus-utilisation-chart>
    </div>

ChartComponent.ts

getStackedChart() {
        const canvas: any = document.getElementById('canvas1');
        const ctx = canvas.getContext('2d');
        
        var data = {
          labels: this.chartdata.buyernames,
         
          datasets: [{
            label: 'Utilised Limit',
            data: this.chartdata.utilisedlimitData,
            backgroundColor: '#22aa99'
          }, {
            label: 'Available Limit',
            data: this.chartdata.availablelimit,
            backgroundColor: '#994499'
          }]
        }
    
        chartJsLoaded$.pipe(take(1)).subscribe(() => {
          setTimeout(() => {
            this.myChart = new Chart(ctx, {
              type: 'bar',
              data: data,
              options: {
                tooltips: {
                  mode: 'index',
                  intersect: true,
                  position: 'custom',
                  yAlign: 'bottom'
                },
                scales: {
                  xAxes: [{
                    stacked: true
                  }],
                  yAxes: [{
                    stacked: false,
                    display: false
                  }]
                }
              }
            });
          })
    
        })
      }

I tried two ways

using view child the chart not created, getElementById chart created but the second chart replacing the first one. but I want two stacked charts side by side how to achieve this

and the current chart taking below 100 values but as per my actual requirment I need to show tootip amount like (1000000, 700000) that too currency format

Like this I tried to acheive https://stackblitz.com/edit/angular-chart-js-j26qhm?file=src%2Fapp%2Fapp.component.html

Please give suggestions

after getting answers I acheived few things

https://stackblitz.com/edit/angular-chart-js-tyggan

2

There are 2 best solutions below

4
On BEST ANSWER

You can use ViewChild to reference html element and use it inside you component. I have also modified few things in your code to toggle charts.

To summarize:

  1. Use ViewChild to access html element in your component
  2. Updated app component to toggle charts as opposed to just adding data
  3. Updated label to accept click event to toggle checkbox

Take a look at this stackblitz.

app.component.html

<label>
  <input type="checkbox" value=1
(change)="chooseEntity($event.target.checked, 1, entityData[0])">
  Microsoft
</label>
<label>
<input type="checkbox" (change)="chooseEntity($event.target.checked, 2, entityData[1])">
  IBM
</label>

<div *ngIf="chartData.length !== 0">
  <app-limus-utilisation-chart *ngFor="let entity of chartData" [chartdata]="entity"></app-limus-utilisation-chart>
</div>

chart.component.html

<div #chartReport>
    <canvas #canvas></canvas>
</div>

chart.component.ts

import {
  Component,
  ElementRef,
  Input,
  OnInit,
  ViewChild,
  ViewEncapsulation
} from "@angular/core";
import { Chart } from "chart.js";

import { OnChanges } from "@angular/core";
@Component({
  selector: "app-limus-utilisation-chart",
  templateUrl: "./chart.component.html",
  encapsulation: ViewEncapsulation.None
})
export class LimusUtilisationChartComponent implements OnInit, OnChanges {
  myChart: Chart;
  @Input() chartdata: any;
  @ViewChild("canvas") stackchartcanvas: ElementRef;

  constructor() {}
  ngOnChanges() {
    this.getStackedChart();
  }

  ngOnInit(): void {
    this.getStackedChart();
  }

  getStackedChart() {
    Chart.Tooltip.positioners.custom = function(elements, position) {
      //debugger;
      return {
        x: position.x,
        y:
          elements[0]._view.base - (elements[0].height() + elements[1].height())
      };
    };

    const canvas: any = this.stackchartcanvas.nativeElement;
    const ctx = canvas.getContext("2d");
    var data = {
      labels: this.chartdata.buyernames,

      datasets: [
        {
          label: "Utilised Limit",
          data: this.chartdata.utilisedlimitData,
          backgroundColor: "#22aa99"
        },
        {
          label: "Available Limit",
          data: this.chartdata.availablelimit,
          backgroundColor: "#994499"
        }
      ]
    };

    setTimeout(() => {
      this.myChart = new Chart(ctx, {
        type: "bar",
        data: data,
        options: {
          tooltips: {
            mode: "index",
            intersect: true,
            position: "custom",
            yAlign: "bottom"
          },
          scales: {
            xAxes: [
              {
                stacked: true
              }
            ],
            yAxes: [
              {
                stacked: false,
                display: false
              }
            ]
          }
        }
      });
    });
  }
}
1
On

The problem is this line here:

    const canvas: any = document.getElementById('canvas1');

You have multiple elements with that ID on the page (Because you did *ngFor), so it always attaches itself to the first element on the page.

Instead of using getElementByID, you should use Angular's built-in @ViewChild.

Like this:

chart.component.html:

<canvas #stackchartcanvas></canvas>

chart.component.ts:

@ViewChild("stackchartcanvas") myCanvas: ElementRef<HTMLCanvasElement>;
....
....
....
getStackedChart() {
    const canvas = this.myCanvas.nativeElement;
}

Stackblitz: https://stackblitz.com/edit/angular-chart-js-kny4en?file=src%2Fapp%2Fchart.component.ts

(Also, in your original code, this.chartData.push() ran EVERY time a checkbox was clicked, even if the checkbox was false, but that's a different, unrelated problem, which has also been fixed.)