import Taro from "@tarojs/taro"; import { View, Canvas } from "@tarojs/components"; import { useEffect, forwardRef, useImperativeHandle } from "react"; const RadarChart: React.FC = forwardRef((props, ref) => { const { data } = props; const maxValue = 100; const levels = 5; const radius = 100; const center = { x: 160, y: 160 }; useEffect(() => { if (data.length > 0) { const { texts, vals } = data.reduce( (res, item) => { const [text, val] = item; return { texts: [...res.texts, text], vals: [...res.vals, val], }; }, { texts: [], vals: [] } ); renderCanvas(texts, vals); } }, [data]); function renderCanvas(labels, values) { const query = Taro.createSelectorQuery(); query .select("#radarCanvas") .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node as HTMLCanvasElement; const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; const dpr = Taro.getWindowInfo().pixelRatio; canvas.width = res[0].width * dpr; canvas.height = res[0].height * dpr; ctx.scale(dpr, dpr); // 启用抗锯齿,消除锯齿 ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; // 设置线条端点样式为圆形,减少锯齿 ctx.lineCap = 'round'; ctx.lineJoin = 'round'; // === 绘制圆形网格 === for (let i = levels; i >= 1; i--) { const r = (radius / levels) * i; ctx.beginPath(); ctx.arc(center.x, center.y, r, 0, Math.PI * 2); if (i % 2 === 1) { ctx.fillStyle = "#fff"; ctx.fill(); } else { ctx.fillStyle = "#CAFCF0"; ctx.fill(); } // 根据层级设置不同的线条颜色,中间圆圈使用更浅的颜色 if (i === 1) { // 最内圈使用最浅的颜色 ctx.strokeStyle = "#E5E5E5"; } else if (i <= 3) { // 中间圆圈使用较浅的颜色 ctx.strokeStyle = "#E0E0E0"; } else { // 外圈使用稍深但仍然较浅的颜色 ctx.strokeStyle = "#D5D5D5"; } // 设置线条宽度为1px,确保清晰 ctx.lineWidth = 1; ctx.stroke(); } // === 坐标轴 & 标签 === labels.forEach((label, i) => { const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; const x = center.x + radius * Math.cos(angle); const y = center.y + radius * Math.sin(angle); // 坐标轴 ctx.beginPath(); ctx.moveTo(center.x, center.y); ctx.lineTo(x, y); // 坐标轴线条也使用较浅的颜色,与圆圈保持一致 ctx.strokeStyle = "#E0E0E0"; ctx.lineWidth = 1; ctx.stroke(); // 标签 const offset = 10; const textX = center.x + (radius + offset) * Math.cos(angle); const textY = center.y + (radius + offset) * Math.sin(angle); ctx.font = "12px sans-serif"; ctx.fillStyle = "#333"; ctx.textBaseline = "middle"; if ( Math.abs(angle) < 0.01 || Math.abs(Math.abs(angle) - Math.PI) < 0.01 ) { ctx.textAlign = "center"; } else if (angle > -Math.PI / 2 && angle < Math.PI / 2) { ctx.textAlign = "left"; } else { ctx.textAlign = "right"; } ctx.fillText(label, textX, textY); }); // === 数据区域 === // ctx.beginPath(); // values.forEach((val, i) => { // const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; // const r = (val / maxValue) * radius; // const x = center.x + r * Math.cos(angle); // const y = center.y + r * Math.sin(angle); // if (i === 0) ctx.moveTo(x, y); // else ctx.lineTo(x, y); // }); // ctx.closePath(); // ctx.fillStyle = "rgba(0,200,180,0.3)"; // ctx.fill(); // ctx.strokeStyle = "#00c8b4"; // ctx.lineWidth = 3; // ctx.stroke(); const baseRadius = (radius / levels) * 1; // 第二个环为起点 const usableRadius = radius - baseRadius; ctx.beginPath(); values.forEach((val, i) => { const angle = ((Math.PI * 2) / labels.length) * i - Math.PI / 2; // 关键修改:值从第二个环开始 const r = baseRadius + (val / maxValue) * usableRadius; const x = center.x + r * Math.cos(angle); const y = center.y + r * Math.sin(angle); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.closePath(); ctx.fillStyle = "rgba(0,200,180,0.3)"; ctx.fill(); ctx.strokeStyle = "#00c8b4"; ctx.lineWidth = 3; ctx.stroke(); }); } useImperativeHandle(ref, () => ({ generateImage: () => new Promise((resolve, reject) => { const query = Taro.createSelectorQuery(); query .select("#radarCanvas") .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node; // ⚠️ 关键:传 canvas,而不是 canvasId Taro.canvasToTempFilePath({ canvas, success: (res) => resolve(res.tempFilePath), fail: (err) => reject(err), }); }); }), })); return ( ); }); export default RadarChart;