#ngx-charts line-chart doesn't update even if new data is available

16 messages · Page 1 of 1 (latest)

snow bluff
#

I have a service which is an Observable that gets updated with new data.
In my custom Chart component I subscribe to that Observable like so:

constructor(private readonly poller: PollerService) {
    this.poller.data$.subscribe(data => {
      this.type = data.sensors[this.id].type;
      this.addMeasurement(data.sensors[this.id].measurements);
    });
  }

  private initChart(): void {
    if (this.charts.length) {
      return;
    }

    const measurement = this.measurements[this.measurements.length - 1];
    const units = measurement.map(channel => channel.unit);

    this.view = [this.width, this.height / units.length];

    this.charts = units.map(unit => ([{
      name: unit,
      series: [],
    }]));
  }

    private toChartData(): void {
      this.initChart();

      const newChartData: any = {};
      for (const measurement of this.measurements) {
        for (const channel of measurement) {
          newChartData[channel.unit] ||= [];

          (newChartData[channel.unit] as Series[]).push({
            value: channel.value,
            name: new DatePipe('it-IT').transform(new Date(channel.timestamp), 'HH:mm:ss.SSS')!,
          });
        }
      }

      for (const chart of this.charts) {
        const unit = chart[0].name;
        chart[0].series = newChartData[unit];
      }

      // array-destructuring reassignment doesn't work and ChangeDetectionRef.detectChanges() doesn't work
    }

  private addMeasurement(measurement: Measurement[]): void {
    this.measurements.push(measurement);

    if (this.measurements.length > this.poller.historyLength) {
      this.measurements.shift();
    }

    this.toChartData();
  }

Essentially, I have multiple charts (which data is valid) inside an array and then I render them using a for-block like so: https://pastebin.com/7ayhdpN3
The chart doesn't get updated, but the span correctly gets updated. How can I fix this bug?

merry bear
#

please post your template here

snow bluff
# merry bear please post your template here
@for (chart of charts; track chart; let i = $index) {
  @if (chart.length && i < charts.length - 1) {
    <span>{{ chart | json }}</span>
    <ngx-charts-line-chart
      [view]="view"
      [results]="chart"
      [animations]="animations"
      [legend]="showLegend"
      [legendTitle]="legendTitle"
      [legendPosition]="legendPosition"
      [xAxis]="showXAxis"
      [yAxis]="showYAxis"
      [showXAxisLabel]="showXAxisLabel"
      [showYAxisLabel]="showYAxisLabel"
      [xAxisLabel]="xAxisLabel"
      [yAxisLabel]="yAxisLabel"
      [timeline]="timeline"
      [showGridLines]="showGridLines"
      [roundDomains]="roundDomains"
      [xAxisTickFormatting]="hideXTicks"
    >
    </ngx-charts-line-chart>
  }
  @else if (chart.length) {
    <ngx-charts-line-chart
      [view]="view"
      [results]="chart"
      [animations]="animations"
      [legend]="showLegend"
      [legendTitle]="legendTitle"
      [legendPosition]="legendPosition"
      [xAxis]="showXAxis"
      [yAxis]="showYAxis"
      [showXAxisLabel]="showXAxisLabel"
      [showYAxisLabel]="showYAxisLabel"
      [xAxisLabel]="xAxisLabel"
      [yAxisLabel]="yAxisLabel"
      [timeline]="timeline"
      [showGridLines]="showGridLines"
      [roundDomains]="roundDomains"
    >
    </ngx-charts-line-chart>
  }
}```
merry bear
#

why chart[0]?
Is chart an array itself?

snow bluff
merry bear
#

lucky guess, try this edit:

for (const chart of this.charts) {
        const unit = chart[0].name;
        chart[0] = {...chart[0], series: newChartData[unit]};
      }
snow bluff
#

i guess i will just do a workaround with behavior subjects

merry bear
#

oh, wait, you said the array is the binding itself.
Try this:

for (const chart of this.charts) {
        const unit = chart[0].name;
        const [first, ...rest] = chart; 
        chart= [{...first, series: newChartData[unit]}, ...rest];
      }
snow bluff
#

ok now it doesn't even update the span

#

i guess this is a good sign? if one gets fixed then the other should get fixed too ig

#

ok no, it was because the this.charts value doesn't get updated, i'll fix that

#

@merry bear tysm! the bug was solved with this new for:

for (let [i, chart] of this.charts.entries()) {
    const unit = chart[0].name;
    const [thisXAxis, ...otherXAxis] = chart;
    chart = [{...thisXAxis, series: newChartData[unit]}, ...otherXAxis];
    this.charts[i] = chart;
}
#

SOLUTION

Modify the for const-of loop inside the toChartData function in order to make reassignment to the this.charts variable mandatory. Essentially you must not modify properties of the object in a sort of "reference way", instead you should "allocate" a new one. (forgive the terms, I mainly program in C/C++ so that's how I understood it).

Old (buggy) solution

for (const chart of this.charts) {
    const unit = chart[0].name;
    chart[0].series = newChartData[unit];
}

Here, this.charts gets implicitly modified since we are accessing a property of one of its elements (FYI, the index is counted as a property in arrays iirc) thus not updating the ngx-charts component correctly.

New (bug-free) solution

Almost working analysis

for (let chart of this.charts) {
    const unit = chart[0].name;
    const [thisXAxis, ...otherXAxis] = chart;
    chart = [{...thisXAxis, series: newChartData[unit]}, ...otherXAxis];
}

Here, the class attribute doesn't get updated since we are reassigning the array element. We have to explicitly modify the charts class attribute in order to update it.

Explicit fix

for (let [i, chart] of this.charts.entries()) {
    const unit = chart[0].name;
    const [thisXAxis, ...otherXAxis] = chart;
    chart = [{...thisXAxis, series: newChartData[unit]}, ...otherXAxis];
    this.charts[i] = chart;
}

Since an Array is an Object, it has the entries function which returns the [key, value] pairs. We use this to get the index of the chart we are modifying and reassign it.

#

@merry bear sorry to ping you again, could you proof check the solution write-up that I wrote and see if that's okay for future reference by other people who could stumble upon this?