How to make a dynamically growing data chart in Chart.JS?

Viewed 222

I am trying to develop a Crash Game, where a multiplier (Y) increases exponentially and dynamically over time (X), causing the chart to re-render at each tick.

You can see an example of the chart game here

TL;DR: I am trying to achieve a "zoom-out" effect of the chart as my ticks increase in values (x,y).

Where my code fails is when ticks data values (x,y, respectively time and multiplier) surpass suggestedMax tick values. The only reason I am using suggestedMax is to have some labels on the chart at the beginning.

I have tried to achieve this by using both line and scatter chart type, but the final outcome is simply unacceptable from a performance point of view.

Here is my code:

const HomePlaygroundView = () => {
  var chart = undefined;
  const chartText = useRef(null);
  let last_tick_received = 0;
  const incrementChart = () => {
    last_tick_received += 100;
  };

  const onServerTickReceived = (multiplier, msLapsed) => {
    // Update chart multiplied

    if (chart.data.datasets[0].data.length >= 100) {
      // Halve the array to save performance (lol)

      for (let i = 1; i < 100; i += 2) {
        console.log("Reducing chart data");
        chart.data.datasets[0].data.splice(i, 1);
      }
    }

    chart.data.datasets[0].data.push({
      x: msLapsed,
      y: multiplier,
    });
    // This is basically my zoom out effect implementation...
    if (multiplier >= 2.5) { // Increase suggestedMax only if bigger data needs to be fit
      chart.options.scales.yAxes[0].ticks.suggestedMax = multiplier;
    }
    if (msLapsed > 9000) { // Same logic as above
      chart.options.scales.xAxes[0].ticks.suggestedMax = msLapsed;
    }

    if (msLapsed < 10000) {
      // Fit msLapsed in the pre-existing 10 seconds labels of x axis (this is a hell of a workaround)
      let willInsertAtIndex = undefined;
      for (let i = 0; i < chart.data.labels.length; i++) {
        let current = chart.data.labels;
        if (current < msLapsed) {
          // Insert at i + 1? Check the next index if it's bigger than msLapsed
          let nextVal = chart.data.labels[i + 1];
          if (nextVal) {
            if (nextVal > msLapsed) {
              willInsertAtIndex = i + 1;
              break;
            }
          } else {
            willInsertAtIndex = i + 1;
            break;
          }
        }
      }
      if (willInsertAtIndex) {
        chart.data.labels.splice(willInsertAtIndex, 0, msLapsed);
      }
    } else {
      chart.data.labels.push(msLapsed);
    }

    // Decimate data every so and so

    chartText.current.innerText = `${multiplier}x`;
    // Re-render canvas
    chart.update();
  };

  useEffect(() => {
    console.log("rendered chart");
    var ctx = document.getElementById("myChart").getContext("2d");
    ctx.height = "350px";
    chart = new Chart(ctx, {
      // The type of chart we want to create
      type: "scatter",

      // The data for our dataset
      data: {
        labels: [...Array(11).keys()].map((s) => s * 1000),
        datasets: [
          {
            label: "testt",
            backgroundColor: "transparent",
            borderColor: "rgb(255, 99, 132)",
            borderWidth: 10,
            showLine: true,
            borderJoinStyle: "round",
            borderCapStyle: "round",
            data: [
              {
                y: 1,
                x: 0,
              },
            ],
          },
        ],
        animation: {
          duration: 0,
        },
        responsiveAnimationDuration: 100, // animation duration after a resize
      },

      // Configuration options go here
      options: {
        spanGaps: true,
        events: [],
        responsive: true,
        maintainAspectRatio: false,
        legend: {
          display: false,
        },
        elements: {
          point: {
            radius: 0,
          },
        },
        scales: {
          xAxes: [
            {
              type: "linear",
              ticks: {
                callback: function (value, index, values) {
                  let s = Math.round(value / 1000);

                  return s.toString() + "s";

                  //return value;
                },
                autoSkipPadding: 100,
                autoSkip: true,
                suggestedMax: 10000,
                stepSize: 100,
                min: 0,
              },
            },
          ],
          yAxes: [
            {
              ticks: {
                // Include a dollar sign in the ticks
                callback: function (value, index, values) {
                  return Math.round(value).toString() + "x"; // Display steps by 0,5
                },
                min: 1,
                suggestedMax: 2.5,
                stepSize: 0.01,
                autoSkip: true,
                autoSkipPadding: 150,
              },
            },
          ],
        },
      },
    });

    let lastTick = 1.0;

    let dateStart = new Date().getTime();

    setTimeout(() => {
      chartText.current.innerText = "Go!";
      setTimeout(() => {
        setInterval(() => {
          let timePassed = new Date().getTime() - dateStart;
          //console.log(timePassed);
          let calculateTick = Math.pow(
            1.01,
            0.00530133800509 * timePassed
          ).toFixed(2);
          console.log(timePassed);
          onServerTickReceived(calculateTick, timePassed);
        }, 50);
      }, 1000);
    }, 2000);
  });

  const classes = useStyles();
  return (
    <div className={classes.canvasContainer}>
      <span ref={chartText} className={classes.canvasText}>
        Ready...?
      </span>
      <canvas id="myChart"></canvas>
    </div>
  );
};

export default HomePlaygroundView;
0 Answers
Related