简体   繁体   中英

Chart.js and Angular 8 - Dynamically updating Chart.js labels and data from *ngfor

I'm relatively new to Angular, but I've been tasked with building what is proving quite a complex Single Page Application (for me, anyway).

On one part of this application, there is a chart - created via using Chart.js - which can be updated dynamically via a select field and input fields.

Here is an example from a flat design of what it should look like:

图表设计

So, the user will add a deposit source via the "Add deposit source" link, which in turn will bring up the option field and input field (which I've already coded) and when the user has chosen an option from the select field and entered a value in the input, the chart should update. Then they can then repeat this process as many times as needed. Additionally, when clicked on, the trash icon should also be able to remove these values from the chart's array as well as remove the fields from the page.

I've been able to get these values to update separately, so I can add a label to the array when a user selects an option, and I can add the value from the input field after the user focuses off the field, but I can't get them to happen together.

Here is the code from my HTML:

            <div class="chart-container">
              <canvas #lineChart id="depositBreakdown" width="300" height="300">{{chart}}</canvas>
            </div>
            <div class="source-container">
                <div *ngFor="let depositSource of depositSources; let depositParent = index" id="{{'source' + depositParent}}" class="deposit-source">
                  <mat-form-field class="dropdown">
                    <mat-select placeholder="Deposit source" (selectionChange)="getLabel($event, chart, label, data)">
                      <mat-option *ngFor="let deposit of deposits; let depositSourceOption = index" [value]="deposit.value" id="{{'option' + depositSourceOption}}">
                        {{ deposit.viewValue }}
                      </mat-option>
                    </mat-select>
                  </mat-form-field>
                  <mat-form-field class="textfield">
                    <input
                      matInput
                      currencyMask
                      [options]="{ align: 'right', prefix: '£ ', thousands: ',', precision: 0 }"
                      placeholder="£ 0"
                      [(ngModel)]="depositSourceAmount"
                      [ngModelOptions]="{ standalone: true }"
                      (focusout)="getData(index, chart, label, data)"
                    />
                  </mat-form-field>
                  <mat-icon (click)="removeDeposit(depositSource)">delete</mat-icon>
                </div>
                <a id="addDepositSource" (click)="appendDeposit()">+ Add Deposit Source</a>
              </div>
        </div>

And the code from my TS file which relates to this:

  deposits: Deposit[] = [
    { value: 'buildersGift', viewValue: 'Builders Gift' },
    { value: 'gift', viewValue: 'Gift' },
    { value: 'loan', viewValue: 'Loan' },
    { value: 'savings', viewValue: 'Savings' },
    { value: 'saleOfProperty', viewValue: 'Sale of Property' },
    { value: 'inheritance', viewValue: 'Inheritance' },
    { value: 'vendorGifted', viewValue: 'Vendor Gifted' },
    { value: 'equity', viewValue: 'Equity' },
    { value: 'forcesHelp', viewValue: 'Forces Help to Buy Loan' },
    { value: 'helpToBuyISA', viewValue: 'Help to Buy ISA' },
    { value: 'porting', viewValue: 'Porting' },
    { value: 'other', viewValue: 'Other' }
  ];

 ngAfterViewInit() {
    this.chart = new Chart(this.chartRef.nativeElement, {
      type: 'doughnut',
      data: {
          labels: [],
          datasets: [{
              label: 'Deposit Sources',
              data: [],
              backgroundColor: [
                  'rgba(254, 11, 48, 1)',
                  'rgba(252, 92, 101, 1)',
                  'rgba(86, 223, 144, 1)',
                  'rgba(186, 223, 86, 1)',
                  'rgba(191, 86, 223, 1)',
                  'rgba(235, 5, 5, 1)',
                  'rgba(43, 203, 186, 1)',
                  'rgba(5, 235, 235, 1)',
                  'rgba(169, 86, 223, 1)',
                  'rgba(86, 160, 223, 1)',
                  'rgba(102, 86, 223, 1)',
                  'rgba(223, 86, 218, 1)',
              ]
          }]
      },
      options: {
        legend: {
          display: false
        },
        scales: {
          xAxes: [{
            display: false
          }],
          yAxes: [{
            display: false
          }],
        }
      }
    });
  }

  getLabel(event, chart, data) {
    let target = event.source.selected._element.nativeElement;
    chart.data.labels.push(target.innerText.trim());
    chart.data.datasets.forEach((dataset) => {
      dataset.data.push(data);
    });
    chart.update();
  }

  getData(event, chart, label, data) {
    data = this.depositSourceAmount;
    chart.data.datasets.forEach((dataset) => {
      dataset.data.push(data);
    });
    chart.update();
  }

    depositSources: depositCreate[] = [];

  appendDeposit() {
    this.depositSources.push(new depositCreate());
  }

  removeDeposit = function(depositSource) {
    this.depositSources.splice(this.depositSources.indexOf(depositSource), 1);
  }

I tried setting up a barebones StackBlitz, though it seems even more problematic than my original code: https://stackblitz.com/edit/angular-zpu7jh

I've been trying to combine my getLabel() and getData() functions into one function, but I've reached an impasse where I've hit the limit of my knowledge. I've tried searching on here and the rest of the web but can't quite find the information to help me nail it. The best I can get is where the labels and values are added to the chart but separate from each other. My main issue is trying to get my head around the nested ngFor loops.

I hope I've explained this sufficiently - I've been working on it all day Friday and all today and I've reached the end of my tether with it. Any help would be greatly appreciated.

The reply Jeremy Thille gave me helped me in the right direction. By installing ng2-charts, I was able to add a chart that allowed me to push the labels and data to the chart much easier:

HTML:

<div class="deposit-container">
            <div class="chart-container">
              <canvas baseChart
              width="290" 
              height="290"
              [data]="doughnutChartData"
              [labels]="doughnutChartLabels"
              [options]="doughnutChartOptions"
              [colors]="doughnutChartColors"
              [chartType]="doughnutChartType"></canvas>
            </div>
            <div class="source-container">
                <div *ngFor="let depositSource of depositSources; let depositParent = index" id="{{'source' + depositParent}}" class="deposit-source">
                  <mat-form-field class="dropdown">
                    <mat-select placeholder="Deposit source" (selectionChange)="getLabel($event)">
                      <mat-option *ngFor="let deposit of deposits; let depositSourceOption = index" [value]="deposit.value" id="{{'option' + depositSourceOption}}">
                        {{ deposit.viewValue }}
                      </mat-option>
                    </mat-select>
                  </mat-form-field>
                  <mat-form-field class="textfield">
                    <input
                      matInput
                      currencyMask
                      [options]="{ align: 'right', prefix: '£ ', thousands: ',', precision: 0 }"
                      placeholder="£ 0"
                      [(ngModel)]="depositSourceAmount[depositParent]"
                      [ngModelOptions]="{ standalone: true }"
                      (focusout)="getData(depositParent)"
                    />
                  </mat-form-field>
                  <mat-icon (click)="removeDeposit(depositSource)">delete</mat-icon>
                </div>
                <a id="addDepositSource" (click)="appendDeposit()">+ Add Deposit Source</a>
              </div>
        </div>

TS:

  public doughnutChartLabels = [];
  public doughnutChartData = [];
  public doughnutChartType = 'doughnut';

  public doughnutChartOptions = {
    legend: {
      display: false
    }
  };

  public doughnutChartColors: Array<any> = [
    { 
      backgroundColor: [
        'rgba(254, 11, 48, 1)',
        'rgba(86, 223, 144, 1)',
        'rgba(186, 223, 86, 1)',
        'rgba(191, 86, 223, 1)',
        'rgba(235, 5, 5, 1)',
        'rgba(43, 203, 186, 1)',
        'rgba(5, 235, 235, 1)',
        'rgba(169, 86, 223, 1)',
        'rgba(252, 92, 101, 1)',
        'rgba(86, 160, 223, 1)',
        'rgba(102, 86, 223, 1)',
        'rgba(223, 86, 218, 1)'
      ]
    }
]

  getLabel(event) {
    let target = event.source.selected._element.nativeElement;
    console.log(target.innerText.trim());
    this.doughnutChartLabels.push(target.innerText.trim());
    this.chart.chart.update();
  }

  getData(depositParent) {
    let data = this.depositSourceAmount[depositParent];
    this.doughnutChartData.push(data);
    this.chart.chart.update();
  }

  depositSources: depositCreate[] = [];

  appendDeposit() {
    this.depositSources.push(new depositCreate());
  }

  removeDeposit = function(depositSource) {
    this.depositSources.splice(this.depositSources.indexOf(depositSource), 1);
  }

I still need to figure out to edit and remove the data from the array from the right place, but that's a separate problem entirely so I'll mark this as solved.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM