React项目中如何在手绘图表上标注最高点与最低点?
React环境下图表极值标注实现方案与库推荐
一、专用库推荐
直接用成熟图表库可以大幅减少自定义逻辑,以下是适配React生态的最优选择:
- Recharts:React专属轻量图表库,原生支持自定义组件标注,API贴合React语法,上手成本低
- Chart.js + react-chartjs-2:通用图表库的React封装,自带数据极值计算能力,搭配
chartjs-plugin-annotation插件可快速实现标注 - D3.js:高度定制化可视化库,适合复杂手绘场景,完全控制绘图与标注逻辑
二、快速实现方案(以Recharts为例)
适合基于结构化数据的图表场景,代码直接复用:
1. 安装依赖
npm install recharts
2. 核心代码
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Dot, Text } from 'recharts'; // 替换为用户绘制生成的实际数据集 const chartData = [ { name: 'Mon', value: 120 }, { name: 'Tue', value: 200 }, { name: 'Wed', value: 150 }, { name: 'Thu', value: 80 }, // 最小值 { name: 'Fri', value: 250 }, // 最大值 { name: 'Sat', value: 180 }, { name: 'Sun', value: 220 }, ]; // 计算极值点 const values = chartData.map(item => item.value); const maxVal = Math.max(...values); const minVal = Math.min(...values); const maxPoint = chartData.find(item => item.value === maxVal); const minPoint = chartData.find(item => item.value === minVal); export default function ExtremesMarkedChart() { return ( <ResponsiveContainer width="100%" height={400}> <LineChart data={chartData}> <CartesianGrid strokeDasharray="3 3" /> <XAxis dataKey="name" /> <YAxis /> <Tooltip /> {/* 基础折线 */} <Line type="monotone" dataKey="value" stroke="#8884d8" strokeWidth={2} /> {/* 标注最大值点+数值 */} <Dot cx={(chartData.indexOf(maxPoint) / (chartData.length - 1)) * 800} cy={(1 - maxVal / (maxVal * 1.2)) * 400} r={6} fill="#ff4d4f" stroke="#fff" strokeWidth={2} /> <Text x={(chartData.indexOf(maxPoint) / (chartData.length - 1)) * 800 + 10} y={(1 - maxVal / (maxVal * 1.2)) * 400 - 10} fill="#ff4d4f"> {maxVal} </Text> {/* 标注最小值点+数值 */} <Dot cx={(chartData.indexOf(minPoint) / (chartData.length - 1)) * 800} cy={(1 - minVal / (maxVal * 1.2)) * 400} r={6} fill="#52c41a" stroke="#fff" strokeWidth={2} /> <Text x={(chartData.indexOf(minPoint) / (chartData.length - 1)) * 800 + 10} y={(1 - minVal / (maxVal * 1.2)) * 400 + 20} fill="#52c41a"> {minVal} </Text> </LineChart> </ResponsiveContainer> ); }
三、自由手绘场景实现(Canvas)
如果是用户在画布上自由绘制曲线,需手动处理坐标记录与极值标注:
import { useRef, useEffect, useState } from 'react'; export default function FreeDrawWithExtremes() { const canvasRef = useRef(null); const [drawPoints, setDrawPoints] = useState([]); const [isDrawing, setIsDrawing] = useState(false); // 开始绘制 const startDraw = (e) => { const canvas = canvasRef.current; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; setIsDrawing(true); setDrawPoints(prev => [...prev, {x, y}]); const ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.moveTo(x, y); }; // 绘制中 const onDraw = (e) => { if (!isDrawing) return; const canvas = canvasRef.current; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; setDrawPoints(prev => [...prev, {x, y}]); const ctx = canvas.getContext('2d'); ctx.lineTo(x, y); ctx.strokeStyle = '#8884d8'; ctx.lineWidth = 2; ctx.stroke(); }; // 结束绘制并标注极值 const stopDraw = () => { setIsDrawing(false); markExtremePoints(); }; // 标注极值点 const markExtremePoints = () => { if (drawPoints.length === 0) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); // Canvas y轴向下,y值越小越靠上(即最大值) const maxPoint = drawPoints.reduce((prev, curr) => curr.y < prev.y ? curr : prev); const minPoint = drawPoints.reduce((prev, curr) => curr.y > prev.y ? curr : prev); // 标注最大值 ctx.fillStyle = '#ff4d4f'; ctx.beginPath(); ctx.arc(maxPoint.x, maxPoint.y, 6, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#333'; ctx.font = '14px Arial'; ctx.fillText(`最高值`, maxPoint.x + 10, maxPoint.y - 10); // 标注最小值 ctx.fillStyle = '#52c41a'; ctx.beginPath(); ctx.arc(minPoint.x, minPoint.y, 6, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillText(`最低值`, minPoint.x + 10, minPoint.y + 20); }; return ( <canvas ref={canvasRef} width={800} height={400} style={{ border: '1px solid #ccc' }} onMouseDown={startDraw} onMouseMove={onDraw} onMouseUp={stopDraw} onMouseLeave={stopDraw} /> ); }
四、关键注意事项
- 结构化数据场景优先用Recharts/Chart.js,避免重复造轮子
- 自由手绘场景需注意Canvas坐标系统(y轴向下,极值判断时需反转逻辑)
- 可结合
useCallback优化绘制性能,避免频繁重渲染
内容的提问来源于stack exchange,提问作者Somebody
相关产品推荐
相关产品推荐

