【问题标题】:Having problems with Chart.js and CanvasChart.js 和 Canvas 有问题
【发布时间】:2021-08-03 06:42:31
【问题描述】:

我目前正在使用 Graph.js 渲染图形,它正在处理初始渲染,但是直到我按下 setTimeformats 按钮以便在同一画布上显示另一个图形之前,它给了我错误:画布已经在使用中。必须先销毁 ID 为“0”的图表,然后才能重新使用画布。我是否正确使用它?我应该如何销毁图表以便在同一画布上使用其他图表?非常感谢您的帮助。

import React, { useRef, useEffect, useState } from "react";
import { historyOptions } from "../chartConfig/chartConfig";
import Chart from 'chart.js/auto';
interface Props{
  data:any
}

const ChartData:React.FC<Props> = ({ data}) => {
  const chartRef = useRef<HTMLCanvasElement | null>(null);
  const { day, week, year, detail } = data;
  const [timeFormat, setTimeFormat] = useState("24h");

  const determineTimeFormat = () => {
    switch (timeFormat) {
      case "24h":
        return day;
      case "7d":
        return week;
      case "1y":
        return year;
      default:
        return day;
    }
  };

  useEffect(() => {
    if (chartRef && chartRef.current && detail) {
  const chartInstance = new Chart(chartRef.current, {
        type: "line",

        data: {
          datasets: [
            {
              label: `${detail.name} price`,
              data: determineTimeFormat(),
              backgroundColor: "rgba(174, 305, 194, 0.5)",
              borderColor: "rgba(174, 305, 194, 0.4",
              pointRadius: 0,
            },
          ],
        },
        options: {
          ...historyOptions,
        },     
      });
       if (typeof chartInstance !== "undefined") chartInstance.destroy();
    }
  });
  const renderPrice = () => {
    if (detail) {
      return (
        <>
          <p className="my-0">${detail.current_price.toFixed(2)}</p>
          <p
            className={
              detail.price_change_24h < 0
                ? "text-danger my-0"
                : "text-success my-0"
            }
          >
            {detail.price_change_percentage_24h.toFixed(2)}%
          </p>
        </>
      );
    }
  };
  return (
    <div  className="bg-white border mt-2 rounded p-3">
      <div>{renderPrice()}</div> 
      <div>
        <canvas ref={chartRef} id="myChart" width={250} height={250}></canvas>
      </div>
      <div className="chart-button mt-1">
        <button
          onClick={() => setTimeFormat("24h")}
          className="btn btn-outline-secondary btn-sm"
        >
          24h
        </button>
        <button
          onClick={() => setTimeFormat("7d")}
          className="btn btn-outline-secondary btn-sm mx-1"
        >
          7d
        </button>
        <button
          onClick={() => setTimeFormat("1y")}
          className="btn btn-outline-secondary btn-sm"
        >
          1y
        </button> 
      </div>
    </div>
  );
};

export default ChartData;

【问题讨论】:

  • 每次你的状态或道具改变时都会触发你的 useEffect:see here。如果这是有意的,那么您可以在旧图表实例上调用.destroy()(如果它存在),或者从它的外观来看,您只想交换第一个数据集中的数据,因此您可以在之后调用.update()改变数据。 api spec here
  • 其实很抱歉,我认为你不能轻易地保留画布实例(react 会在每次渲染时重新创建它),所以.update() 不会按预期工作。最好销毁它。
  • @CallumMorrisson 我已经更新了代码,可以这样工作

标签: javascript reactjs typescript


【解决方案1】:

解决此问题的一种方法是使用新的状态变量和useEffect,以便在每次 timeFormat 更改时快速删除并重新创建画布元素。这里的一些关键点:

  • 正如 @CallumMorrisson 所提到的,为了理解这种方法,阅读和理解 React 文档中的 this section 关于完全跳过 useEffect 钩子非常重要。
  • 直接在useEffect 中使用daynameweekyear 属性而不是整个data 变量可确保图表实例仅在必要时重新创建,而不是在每次渲染。函数determineTimeFormat 也是如此,如果可能,这些类型的函数应在组件范围之外定义。
const determineTimeFormat = (
  timeFormat: string,
  day: any,
  week: any,
  year: any
) => {
  switch (timeFormat) {
    case "24h":
      return day;
    case "7d":
      return week;
    case "1y":
      return year;
    default:
      return day;
  }
};

interface Props {
  data: any
}

const ChartData: React.FC<Props> = ({ data }) => {
  const chartCanvasRef = useRef<HTMLCanvasElement | null>(null);
  const { day, week, year, detail } = data;
  const { name } = detail;
  const [timeFormat, setTimeFormat] = useState("24h");
  const [isRebuildingCanvas, setIsRebuildingCanvas] = useState(false);

  // remove the canvas whenever timeFormat changes
  useEffect(() => {
    setIsRebuildingCanvas(true);
  }, [timeFormat]); // timeFormat must be present in deps array for this to work

  /* if isRebuildingCanvas was true for the latest render, 
    it means the canvas element was just removed from the dom. 
    set it back to false to immediately re-create a new canvas */
  useEffect(() => {
    if (isRebuildingCanvas) {
      setIsRebuildingCanvas(false);
    }
  }, [isRebuildingCanvas]);

  useEffect(() => {
    const chartCanvas = chartCanvasRef.current
    if (isRebuildingCanvas || !chartCanvas) {
      return;
    }
    const chartInstance = new Chart(chartRef.current, {
      type: "line",
      data: {
        datasets: [
          {
            label: `${name} price`,
            data: determineTimeFormat(timeFormat, day, week, year),
            backgroundColor: "rgba(174, 305, 194, 0.5)",
            borderColor: "rgba(174, 305, 194, 0.4",
            pointRadius: 0,
          },
        ],
      },
      options: {
        ...historyOptions,
      },
    });
    return () => {
      chartInstance.destroy();
    }
  }, [day, isRebuildingCanvas, name, timeFormat, week, year]);
  return (
    <>
      {isRebuildingCanvas ? undefined : (
        <canvas ref={chartCanvasRef} id='myChart' width={250} height={250} />
      )}
      <button onClick={() => setTimeFormat("24h")}>24h</button>
      <button onClick={() => setTimeFormat("7d")}>7d</button>
      <button onClick={() => setTimeFormat("1y")}>1y</button>
    </>
  );
};

export default ChartData;

【讨论】:

  • 谢谢你,我的家伙
猜你喜欢
  • 2022-08-08
  • 2023-03-31
  • 2018-01-26
  • 1970-01-01
  • 2017-01-22
  • 1970-01-01
  • 2017-08-05
  • 2012-01-20
  • 1970-01-01
相关资源
最近更新 更多