Tom
Tom

Reputation: 8681

Graph not rendering series correctly using angular 4

I have implemented spline chart using angular 4. I am trying to add series to the chart on click of a button. As the user clicks the apply button, a series should be added to the graph. The problem that I am having the graph doesn't refresh to show the added series. The user has to clear the browser history and reload the page to see the newly added series.

One issue what i noticed with the code is that , the code to add the series was written in the OnInit method which means it was only firing once. I changed it to be called every time the page loads. The parent component passes the result object to the child component that hosts the graphs. The results component contains the data and series object.

The issue that I am having with the new code is that when the user clicks the apply button it tries to add the same series that it added before and bind those series to the chart. As a result, duplicate series are added to the chart. Could some body tell me what the problem could be ?

Parent Component

import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { RunService, NavBarService } from '@wtw/platform/services';
import { Base } from '@wtw/toolkit';
import { NpvAnalysis, EvAnalysis } from '../../../shared/models/results';
import { Dto } from '@wtw/platform/api';
import { Strategy, StressTestAnalysis, CaptivesRun, EndingSurplus } from '../../../api/dtos';
import { RunModel } from '@wtw/platform/api/dtos';

@Component({
  selector: 'app-results',
  templateUrl: './results.component.html'
})
export class ResultsComponent extends Base.ReactiveComponent implements OnInit {
  run: CaptivesRun;
  stressTestResults: Array<StressTestAnalysis> = [];
  forceRedraw: { value: number };
  private _baseRun: Dto.RunModel;

  constructor(
    private _runService: RunService,
    private _navBarService: NavBarService,
    private _translate: TranslateService,
  ) {
    super();
  }

  ngOnInit() {
    this._subscriptions = [
      this._runService.activeRun.subscribe((r: any) => {
        this._processRun(r);
      }),
      this._runService.currencyConverted.subscribe(r => {
        this._processRun(r);
        this.save();
        this.forceRedraw = { value: Math.random() * 10000 };
      }),
      this._navBarService.downloadReportEvent.subscribe(x => {
        this.downloadReport();
      })
    ];
  }

  downloadReport() {
    console.log('download report');
  }

  applyChange(event: any) {
    this.run.strategies.splice(event.index, 1, event.strategy);
    this._baseRun.data = this.run;
    this._runTrigger2(this._baseRun, event.index);
  }

  save() {
    this._baseRun.data = this.run;
    this._runService.persist(this._baseRun.runId, this.run, this._baseRun.currencyInfo).uiSignal('save').subscribe(x => {
      this._processResults(this.run.strategies);
    });
  }

  private _runTrigger2(r: Dto.RunModel, strategyIndex: number) {
    this._runService.executeTrigger(r.runId, this.run, { number: 2, param: strategyIndex.toString() }, r.currencyInfo)
      .uiSignal('trigger 2')
      .subscribe(x => {
        this.run = x.data;
        this._processResults(x.data.strategies);
      });
  }

  private _processRun(r: RunModel) {
    this._baseRun = r;
    this.run = r.data as CaptivesRun;

    // Initialising the data
    if (this.run.strategies) {
      if (!this.run.strategies[0].results) {
        this._runTrigger2(this._baseRun, 0);
      } else {
        this._processResults(this.run.strategies);
      }
    }
  }

  private _processResults(strategies: Array<Strategy>) {

    this.stressTestResults = new Array();
    const strategyTranslation = this._translate.instant('CAPTIVES.RESULTS.COMMON.STRATEGY');

    const getStrategyName = (strategy: Strategy, index: number) => {
      let name = this._translate.instant('CAPTIVES.RESULTS.COMMON.BASE_STRATEGY');
      if (index > 0) {
        name = strategyTranslation + ' ' + index;
      }
      return name;
    };

    strategies.forEach((strategy, index) => {
      const strategyName = getStrategyName(strategy, index);
      const results = strategy.results;
      this.stressTestResults.push(results.stressResult);
    });
  }
}

Child component

import { Component, OnInit, Input } from '@angular/core';
import { StressTestAnalysis } from '../../../../api/dtos';
import { ReactiveComponent } from '@wtw/toolkit/src/utils/base.component';

export interface ChartSeries {
  data: number[];
  name: string;
  color: string;
}

export interface YAxisSeries {
  yaxis: number[];
}

@Component({
  selector: 'app-stress-test-analysis',
  templateUrl: './stress-test-analysis.component.html'
})

export class StressTestAnalysisComponent extends ReactiveComponent implements OnInit {
  isExpanded = false;
  showTable = true;
  public chartSeries: Array<ChartSeries> = [];
  yAxisSeries: Array<YAxisSeries> = [];
  yAxisData: number[] = [];
  minYAxis: number;
  maxYAxis: number;
  seriesName: string;
  public results: Array<StressTestAnalysis> = [];
  private _stressResults: Array<StressTestAnalysis> = [];

  @Input() set stressResults(value: Array<StressTestAnalysis>) {
    this.results = value;
    this.addSeries();
    let minY = Math.min(...this.yAxisSeries.map(el => Math.min(...el.yaxis)));
    let maxY = Math.max(...this.yAxisSeries.map(el => Math.max(...el.yaxis)));
    this.generateYAxisArray(minY, maxY);
  }

  constructor( ) { super(); }

  ngOnInit() {
  }

  private addSeries() {
    if (this.results === null) {
      return;
    }


    this.results.forEach(element => {
      if (element.data !== null)
        this.chartSeries.push({ data: element.data, name: element.seriesName, color: element.color });
       if (element.yaxis !== null)
        this.yAxisSeries.push({ yaxis: element.yaxis });
    });
  }

  //Function that generates the array based on the min and max derived from the previous method
  private generateYAxisArray(min: any, max: any) {
    let count = min;
    for (count = min; count <= max; count = count + 500000) {
      this.yAxisData.push(count);
    }
  }


}

Child component html

<div class="card">
  <!-- Stress Test Analysis -->
  <div class="card-header" role="tab" id="sta_heading">
    <a data-toggle="collapse" (click)="isExpanded = !isExpanded" href="javascript:void(0);" role="button" [attr.aria-expanded]="isExpanded"
      aria-controls="accordion3" [ngClass]="{'collapsed': !isExpanded}">
      <h5 class="mb-0">{{'CAPTIVES.RESULTS.STA.TITLE'|translate}}</h5>
    </a>
  </div>
  <div [ngClass]="{'show': isExpanded}" id="accordion3" class="collapse" role="tabpanel" aria-labelledby="accordion3_heading"
    data-parent="#accordion" [attr.aria-expanded]="isExpanded">
    <div class="card-body">
      <ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
        <li class="nav-item">
          <a href="javascript:void(0);" [ngClass]="!showTable ? '' : 'active' " class="nav-link " id="table-tab" data-toggle="pill"
                        role="tab" aria-controls="table" (click)="showTable = !showTable" aria-selected="true">{{'CAPTIVES.RESULTS.COMMON.TABLE'|translate}}</a>
        </li>
        <li class="nav-item">
         <a href="javascript:void(0);" [ngClass]="showTable ? '' : 'active'  " class="nav-link" id="chart-tab" data-toggle="pill"
            role="tab" aria-controls="chart" (click)="showTable = !showTable" aria-selected="false">{{'CAPTIVES.RESULTS.COMMON.CHART'|translate}}</a>
        </li>
      </ul>
      <div class="tab-content" id="pills-tabContent">
        <!-- sta table -->
        <div *ngIf="showTable" class="tab-pane fade show active" id="base-strategy-table--sta" role="tabpanel" aria-labelledby="table-tab">
          <div class="tb-container">
            <div class="tb-row d-flex flex-row">
              <div class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6"></div>
              <div *ngFor="let result of results;" class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">
                <h6>{{'CAPTIVES.RESULTS.STA.AVERAGE_SURPLUS'|translate}}</h6>
              </div>
            </div>
            <div class="tb-row d-flex flex-row">
              <div class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">{{'CAPTIVES.RESULTS.STA.CURRENT_LEVEL_CAPTILIZATION'|translate}}</div>
              <div *ngFor="let result of results;" class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">{{result?.curlevel|percent:'.1-2'}}</div>
            </div>
            <div class="tb-row d-flex flex-row">
              <div class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">{{'CAPTIVES.RESULTS.STA.TEN_PERCENT_LESS_CURRENT_LEVEL_CAPTILIZATION'|translate}}</div>
              <div *ngFor="let result of results;" class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">{{result?.decrease|percent:'.1-2'}}</div>
            </div>
            <div class="tb-row d-flex flex-row">
              <div class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">{{'CAPTIVES.RESULTS.STA.TEN_PERCENT_MORE_CURRENT_LEVEL_CAPTILIZATION'|translate}}</div>
              <div *ngFor="let result of results;" class="tb-cell col-sm-6 col-md-3 col-lg-2 col-6">{{result?.increase|percent:'.1-2'}}</div>
            </div>
          </div>
        </div>
        <!-- sta table End -->
        <!-- sta Chart -->
        <div *ngIf="!showTable" class="tab-pane base-strategy-chart fade show active" id="base-strategy-chart--nva" role="tabpanel"
          aria-labelledby="chart-tab">
          <div class="tb-container">

            <div class="tb-row d-flex flex-row">
              <div class="tb-cell col-12 pt-5">
               <splinechart [series]="chartSeries" [yaxisdata]="yAxisData">
               </splinechart>
                </div>

            </div>
          </div>

          <!-- sta Chart End -->
        </div>
      </div>
    </div>
  </div>
  <!-- Card + Stress Test Analysis End-->

Spline chart component

import { Component, Input, OnChanges } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';

@Component({
    selector: 'splinechart',
    template: '<chart [options]="options" (load)="getInstance($event.context)"></chart>',
    styles: [`
    chart {
        display: block;
        width: 100% !important;
         padding:0;
      }`]
})

export class SplineChartComponent implements OnChanges {
    public options: any;
    chart: any;

    @Input() public series: any;
    @Input() public yaxisdata: any;
    @Input() public selectedRating: string = '';

    constructor(private _translate: TranslateService) {
        this.options = {
            credits: {
                enabled: false
            },
            chart: {
                type: 'spline'
            },
            title: {
                text: ''
            },
            subtitle: {
                text: ''
            },
            legend: {
                layout: 'horizontal',
                margin: 25,
                itemMarginTop: 0,
                symbolRadius: 0,
                symbolHeight: 20,
                symbolWidth: 20,
                useHTML: true,
                    title: {
                    text: this._translate.instant('CAPTIVES.RESULTS.COMMON.GRAPH_LEGEND_TITLE'),
                    margin: 50,
                    style: {
                        fontStyle: 'italic',
                        fontWeight: 'normal'
                    }
                },
                align: 'right',
                verticalAlign: 'bottom',
            },
            xAxis: {
                title: {
                    text: this._translate.instant('CAPTIVES.RESULTS.STA.GRAPH_XAXIS')
                }
            },
            yAxis: {
                title: {
                    text: this._translate.instant('CAPTIVES.RESULTS.STA.GRAPH_YAXIS')
                }
            },

            tooltip: {

            },
            plotOptions: {
                series: {
                    cursor: 'pointer',
                    events: {

                        legendItemClick: function() {
                            const elements = document.querySelectorAll('.highcharts-legend-item path');
                            for (let i = 0; i < elements.length; i++) {
                                elements[i].setAttribute('stroke-width', '20');
                                elements[i].setAttribute('stroke-height', '20');
                            }
                            this.chart.redraw();
                        }

                    },

                    allowPointSelect: true,

                },
                spline: {
                    lineWidth: 2,
                    states: {
                        hover: {
                            lineWidth: 3
                        }
                    },
                    marker: {
                        enabled: true,
                        symbol: 'circle'

                    },
                }
            },
            series: [
                {
                    showInLegend: false
                }
            ]
        };
    }

    getInstance(chartInstance): void {
        this.chart = chartInstance;
        this.redraw();
    }

    ngOnChanges(data: any) {
        if (!data.series.currentValue || !this.chart) return;
        data.series.currentValue.map(s => {
            this.chart.addSeries(s);
        });
        this.chart.reflow();
    }

    redraw() {
        if (!this.chart) return;
        this.chart.yAxis[0].categories = this.yaxisdata;

        this.series.map(s => {
            if (s !== null)
                this.chart.addSeries(s);
        });

        const elements = document.querySelectorAll('.highcharts-legend-item path');
        for (let i = 0; i < elements.length; i++) {
            elements[i].setAttribute('stroke-width', '20');
            elements[i].setAttribute('stroke-height', '20');
        }
        this.chart.redraw();

    }

}

Upvotes: 0

Views: 339

Answers (1)

Kacper Madej
Kacper Madej

Reputation: 7886

  1. Are you using any Angular wrapper for Highcharts? There's one official: https://github.com/highcharts/highcharts-angular and there are some unofficial like: https://www.npmjs.com/package/angular2-highcharts Those will help with dynamic events like adding new series and with general Highcharts usage in Angular.

  2. The code this.chart.yAxis[0].categories = this.yaxisdata; is not good. If you want to update categories for an axis safely then use Axis.update().

  3. The legend item change in your legendItemClick might not be working - the states of the items have API docs:

Upvotes: 1

Related Questions