Javascript Chart.js和Canvas有问题
我目前正在使用Graph.js渲染图形,它正在进行初始渲染,但在我按setTimeformats按钮以便在同一画布上显示另一个图形之前,它给了我一个错误:画布已经在使用中。必须先销毁ID为“0”的图表,然后才能重新使用画布。我用得对吗?如何销毁图表以便在同一画布上使用其他图形?非常感谢您的帮助Javascript Chart.js和Canvas有问题,javascript,reactjs,typescript,Javascript,Reactjs,Typescript,我目前正在使用Graph.js渲染图形,它正在进行初始渲染,但在我按setTimeformats按钮以便在同一画布上显示另一个图形之前,它给了我一个错误:画布已经在使用中。必须先销毁ID为“0”的图表,然后才能重新使用画布。我用得对吗?如何销毁图表以便在同一画布上使用其他图形?非常感谢您的帮助 import React, { useRef, useEffect, useState } from "react"; import { historyOptions } from &
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;
import React,{useRef,useffect,useState}来自“React”;
从“./chartConfig/chartConfig”导入{historyOptions};
从“Chart.js/auto”导入图表;
界面道具{
数据:任何
}
const ChartData:React.FC=({data})=>{
const chartRef=useRef(null);
常数{日、周、年、细节}=数据;
const[timeFormat,setTimeFormat]=使用状态(“24小时”);
常量determineTimeFormat=()=>{
交换机(时间格式){
案例“24小时”:
回归日;
案例“7d”:
返回周;
案例“1y”:
回归年;
违约:
回归日;
}
};
useffect(()=>{
if(chartRef&&chartRef.current&&detail){
const chartInstance=新图表(chartRef.current{
键入:“行”,
数据:{
数据集:[
{
标签:`${detail.name}price`,
数据:determineTimeFormat(),
背景色:“rgba(1743051940.5)”,
边框颜色:“rgba(1743051940.4”,
点半径:0,
},
],
},
选项:{
…历史选项,
},
});
if(typeof chartInstance!=“未定义”)chartInstance.destroy();
}
});
常数renderPrice=()=>{
如果(细节){
返回(
${detail.current_price.toFixed(2)}
{详细信息.价格变动百分比\ 24小时固定(2)}%
);
}
};
返回(
{renderPrice()}
setTimeFormat(“24小时”)}
className=“btn btn大纲辅助btn sm”
>
24小时
setTimeFormat(“7d”)}
className=“btn btn大纲辅助btn sm mx-1”
>
7d
setTimeFormat(“1y”)}
className=“btn btn大纲辅助btn sm”
>
1y
);
};
导出默认图表数据;
解决此问题的一种方法是使用新的状态变量和useffect
在每次时间格式更改时快速删除并重新创建画布元素。这里有一些要点:
- 正如@CallumMorrisson所提到的,为了理解这种方法,阅读和理解React文档中关于跳过
钩子的内容是非常重要的useffect
- 在
中直接使用useffect
、day
、name
、week
属性,而不是使用整个year
变量,可以确保仅在必要时重新创建图表实例,而不是在每次渲染时都重新创建。函数data
,这些类型的如果可能,应在组件范围外定义功能determineTimeFormat
const determineTimeFormat=(
时间格式:字符串,
日期:任何,
周:任何,
年份:有吗
) => {
交换机(时间格式){
案例“24小时”:
回归日;
案例“7d”:
返回周;
案例“1y”:
回归年;
违约:
回归日;
}
};
界面道具{
数据:任何
}
const ChartData:React.FC=({data})=>{
const chartCanvasRef=useRef(null);
常数{日、周、年、细节}=数据;
const{name}=detail;
const[timeFormat,setTimeFormat]=使用状态(“24小时”);
const[isRebuildingCanvas,setIsRebuildingCanvas]=useState(false);
//每当timeFormat更改时,请删除画布
useffect(()=>{
setIsRebuildingCanvas(true);
},[timeFormat]);//要使其工作,deps数组中必须存在timeFormat
/*如果最新渲染的isRebuildingCanvas为true,
这意味着canvas元素刚刚从dom中删除。
将其设置回false可立即重新创建新画布*/
useffect(()=>{
if(isRebuildingCanvas){
setIsRebuildingCanvas(false);
}
},[isRebuildingCanvas]);
useffect(()=>{
const chartCanvas=chartCanvasRef.current
如果(isRebuildingCanvas | | |!chartCanvas){
返回;
}
const chartInstance=新图表(chartRef.current{
键入:“行”,
数据:{
数据集:[
{
标签:`${name}price`,
数据:determineTimeFormat(时间格式、天、周、年),
背景色:“rgba(1743051940.5)”,
边框颜色:“rgba(1743051940.4”,
点半径:0,
},
],
},
选项:{
…历史选项,
},
});
return()=>{
chartInstance.destroy();
}
},[天,isRebuildingCanvas,名称,时间格式,周,年];
返回(
{isRebuildingCanvas?未定义:(
)}
setTimeFormat(“24小时”)}>24小时
setTimeFormat(“7d”)}>7d
setTimeFormat(“1y”)}>1y
);
};
导出默认图表数据;
每次状态或道具更改时,都会触发useEffect:。如果需要,则可以对旧图表实例(如果存在)调用.destroy()
,或者从外观上看,您只想交换第一个数据集中的数据,因此可以调用.update()
更改数据后。事实上,很抱歉,我不认为您可以轻松保留画布实例(react将在每次渲染时重新创建它),因此.update()
将无法按预期工作。最好将其销毁。@CallumMorrisson I ha
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;