Lightning JS Chart causing crashes and not showing data correctly

0

Issue

I have a component that I want to show a graph with multiple line series representing price changes over the last 24 hours. I have an endpoint that sends this data and I use the code below to show it.

One of the issues comes from errors seeming to come from the library itself meaning the graph will not even show up. Errors from the console when I load the page.

Other times, the page will load for a second and then go white and drain enough CPU to cause a crash.

The few times that the graph actually shows up on screen, it does not show any lines until the lines 81-85 are uncommented which it then shows the lines but does not zoom in on them leaving a mess on the screen.

Any help would be much appreciated.

/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-magic-numbers */
import React, { useEffect, useState } from "react";
import { LegendBoxBuilders, lightningChart, Themes } from "@arction/lcjs";
import "./TopCurrencyGraph.css";
import axios from "axios";
export interface data {
  data: dataPoint[];
}

export interface dataPoint {
  currency: string;
  percentage: number;
  timestamp: string;
}

interface graphPoint {
  x: number;
  y: number;
}

const TopCurrencyGraph = () => {
  const historicalAddr = `http://${
    process.env.back || "localhost:8000"
  }/historical24hChangeData`;

  useEffect(() => {
    const map: { [name: string]: graphPoint[] } = {};
    axios
      .get(historicalAddr)
      .then((res) => {
        const { points } = res.data;
        const pointList = points as dataPoint[];
        pointList.forEach((obj) => {
          const newPoint = {
            x: new Date(obj.timestamp).getTime() * (60 * 24),
            y: obj.percentage * 100,
          };
          if (obj.currency in map) {
            map[obj.currency].push(newPoint);
          } else {
            map[obj.currency] = [newPoint];
          }
        });
      })
      .catch((err) => {
        console.log(err, historicalAddr);
      });
    const chart = lightningChart().ChartXY({
      theme: Themes.lightNew,
      container: "currency-graph",
    });
    chart.setTitle("Top Currencies");
    chart.getDefaultAxisX().setTitle("Time");
    chart.getDefaultAxisY().setTitle("Percentage Change");

    const entries = Object.entries(map);
    const names = entries.map(([a, _b]) => a);
    const lists = entries.map(([_, b]) => b);

    const seriesArray = new Array(5).fill(null).map((_, idx) =>
      chart
        .addLineSeries({
          dataPattern: {
            pattern: "ProgressiveX",
          },
        })
        // eslint-disable-next-line arrow-parens
        .setStrokeStyle((stroke) => stroke.setThickness(1))
        .setName(names[idx])
    );

    seriesArray.forEach((series, idx) => {
      if (idx === 1) {
        series.add(lists[idx]);
      }
    });

    chart.addLegendBox(LegendBoxBuilders.HorizontalLegendBox).add(chart);

    return () => {
      chart.dispose();
    };
  }, []);

  // done thnx
  return (
    <div className="graph-container">
      <div id="currency-graph" className="graph-container"></div>
    </div>
  );
};

export default TopCurrencyGraph;


Solution

Your code looks syntax wise correct, but I believe you are running into issues due to not managing asynchronous code (axios getting data from your endpoint) properly.

const map: { [name: string]: graphPoint[] } = {};
axios
   .get(historicalAddr)
   .then((res) => {
      // This code is NOT executed immediately, but only after some time later.
      ...
   })

// This code and everything below is executed BEFORE the code inside `then` block.
// Because of this, you end up supplying `undefined` or other incorrect values to series / charts which shows as errors.
const chart = lightningChart().ChartXY({
   theme: Themes.lightNew,
   container: "currency-graph",
});

You might find it useful to debug the values you supply to series, for example like below. I think the values are not what you would expect.

seriesArray.forEach((series, idx) => {
   if (idx === 1) {
      console.log('series.add', lists[idx])
      series.add(lists[idx]);
   }
});

Improvement suggestion

Here’s my attempt at modifying the code you supplied to manage the asynchronous data loading correctly, by moving all code that relies on the data after the data is processed.

/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-magic-numbers */
import React, { useEffect, useState } from "react";
import { LegendBoxBuilders, lightningChart, Themes } from "@arction/lcjs";
import "./TopCurrencyGraph.css";
import axios from "axios";
export interface data {
  data: dataPoint[];
}

export interface dataPoint {
  currency: string;
  percentage: number;
  timestamp: string;
}

interface graphPoint {
  x: number;
  y: number;
}

const TopCurrencyGraph = () => {
  const historicalAddr = `http://${
    process.env.back || "localhost:8000"
  }/historical24hChangeData`;

  useEffect(() => {
    const chart = lightningChart().ChartXY({
      theme: Themes.lightNew,
      container: "currency-graph",
    });
    chart.setTitle("Top Currencies");
    chart.getDefaultAxisX().setTitle("Time");
    chart.getDefaultAxisY().setTitle("Percentage Change");

    const seriesArray = new Array(5).fill(null).map((_, idx) =>
      chart
        .addLineSeries({
          dataPattern: {
            pattern: "ProgressiveX",
          },
        })
        // eslint-disable-next-line arrow-parens
        .setStrokeStyle((stroke) => stroke.setThickness(1))
    );

    chart.addLegendBox(LegendBoxBuilders.HorizontalLegendBox).add(chart);

    axios
      .get(historicalAddr)
      .then((res) => {
        const { points } = res.data;
        const pointList = points as dataPoint[];
        const map: { [name: string]: graphPoint[] } = {};
        pointList.forEach((obj) => {
          const newPoint = {
            x: new Date(obj.timestamp).getTime() * (60 * 24),
            y: obj.percentage * 100,
          };
          if (obj.currency in map) {
            map[obj.currency].push(newPoint);
          } else {
            map[obj.currency] = [newPoint];
          }
        });

        const entries = Object.entries(map);
        const names = entries.map(([a, _b]) => a);
        const lists = entries.map(([_, b]) => b);

        seriesArray.forEach((series, idx) => {
          series.setName(names[idx])
          if (idx === 1) {
            series.add(lists[idx]);
          }
        });
      })
      .catch((err) => {
        console.log(err, historicalAddr);
      });

    return () => {
      chart.dispose();
    };
  }, []);

  // done thnx
  return (
    <div className="graph-container">
      <div id="currency-graph" className="graph-container"></div>
    </div>
  );
};

export default TopCurrencyGraph;

Answered By – Niilo Keinänen

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave A Reply

Your email address will not be published.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More