feat: 问卷调查
This commit is contained in:
183
src/components/Radar/index.tsx
Normal file
183
src/components/Radar/index.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import Taro, { useReady } from "@tarojs/taro";
|
||||
import { View, Canvas, Button } from "@tarojs/components";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const RadarChart: React.FC = () => {
|
||||
const labels = [
|
||||
"正手球质",
|
||||
"正手控制",
|
||||
"反手球质",
|
||||
"反手控制",
|
||||
"底线相持",
|
||||
"场地覆盖",
|
||||
"发球接发",
|
||||
"接随机球",
|
||||
"战术设计",
|
||||
];
|
||||
const values = [50, 75, 60, 20, 40, 70, 65, 35, 75];
|
||||
const maxValue = 100;
|
||||
const levels = 4;
|
||||
const radius = 100;
|
||||
const center = { x: 160, y: 160 };
|
||||
|
||||
useReady(() => {
|
||||
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.getSystemInfoSync().pixelRatio;
|
||||
canvas.width = res[0].width * dpr;
|
||||
canvas.height = res[0].height * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
// 绘制网格
|
||||
ctx.strokeStyle = "#ddd";
|
||||
// for (let i = 1; i <= levels; i++) {
|
||||
// const r = (radius / levels) * i;
|
||||
// ctx.beginPath();
|
||||
// labels.forEach((_, j) => {
|
||||
// const angle = ((Math.PI * 2) / labels.length) * j - Math.PI / 2;
|
||||
// const x = center.x + r * Math.cos(angle);
|
||||
// const y = center.y + r * Math.sin(angle);
|
||||
// if (j === 0) ctx.moveTo(x, y);
|
||||
// else ctx.lineTo(x, y);
|
||||
// });
|
||||
// ctx.closePath();
|
||||
// ctx.stroke();
|
||||
// }
|
||||
|
||||
for (let i = 1; i <= levels; i++) {
|
||||
const r = (radius / levels) * i;
|
||||
ctx.beginPath();
|
||||
labels.forEach((_, j) => {
|
||||
const angle = ((Math.PI * 2) / labels.length) * j - Math.PI / 2;
|
||||
const x = center.x + r * Math.cos(angle);
|
||||
const y = center.y + r * Math.sin(angle);
|
||||
if (j === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
});
|
||||
ctx.closePath();
|
||||
|
||||
// === 偶数环填充颜色 ===
|
||||
if (i % 2 === 0) {
|
||||
ctx.fillStyle = "rgba(0, 150, 200, 0.1)"; // 浅色填充,透明度可调
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// 保留线条
|
||||
ctx.strokeStyle = "#bbb"; // 边框颜色
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 坐标轴 & 标签
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = "12px sans-serif";
|
||||
// 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 = "#bbb";
|
||||
// ctx.stroke();
|
||||
// ctx.fillText(
|
||||
// label,
|
||||
// x + Math.cos(angle) * 20,
|
||||
// y + Math.sin(angle) * 20
|
||||
// );
|
||||
// });
|
||||
|
||||
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 = "#bbb";
|
||||
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";
|
||||
|
||||
console.log(label, angle)
|
||||
|
||||
// 根据角度调整文字对齐方式
|
||||
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 = 4;
|
||||
ctx.stroke();
|
||||
});
|
||||
});
|
||||
|
||||
// 保存为图片
|
||||
const saveImage = () => {
|
||||
Taro.canvasToTempFilePath({
|
||||
canvasId: "radarCanvas",
|
||||
success: (res) => {
|
||||
Taro.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => Taro.showToast({ title: "保存成功" }),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<Canvas
|
||||
type="2d"
|
||||
id="radarCanvas"
|
||||
style={{ width: "320px", height: "320px", background: "transparent" }}
|
||||
/>
|
||||
{/* <Button onClick={saveImage}>保存为图片</Button> */}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default RadarChart;
|
||||
Reference in New Issue
Block a user