如何实现交互式峰谷时段标注环形图表?求React/Bootstrap相关框架
实现24小时分段交互式峰谷时段标注的方案
React 实现方案
自定义SVG组件(推荐)
不需要依赖复杂第三方库,直接用SVG绘制24个扇形分段,结合React状态管理实现点击切换逻辑:
- 状态设计:用数组
hourStates存储24个时段的状态,每个元素取值0(off-peak)、1(mid-peak)、2(on-peak),初始全为0。 - 切换逻辑:点击对应扇形时,将该位置状态加1后取模3,实现循环切换。
- 样式映射:定义三种状态对应的颜色,根据当前状态渲染。
核心代码片段
import { useState } from 'react'; const PeakHourPicker = () => { // 初始化24个时段为off-peak const [hourStates, setHourStates] = useState(Array(24).fill(0)); // 状态-颜色映射 const stateColors = { 0: '#e0e0e0', // off-peak 1: '#ffcc80', // mid-peak 2: '#ef5350' // on-peak }; const handleHourClick = (index) => { const newStates = [...hourStates]; // 循环切换状态:0→1→2→0 newStates[index] = (newStates[index] + 1) % 3; setHourStates(newStates); }; // 每个扇形占15度(360/24) const sectorAngle = 360 / 24; return ( <div className="peak-hour-picker" style={{ position: 'relative', width: '300px', height: '300px' }}> <svg width="100%" height="100%" viewBox="0 0 300 300"> <circle cx="150" cy="150" r="120" fill="#f5f5f5" /> {hourStates.map((state, index) => { const startAngle = index * sectorAngle; const endAngle = startAngle + sectorAngle; // 计算扇形路径坐标 const startX = 150 + 120 * Math.cos((startAngle * Math.PI) / 180); const startY = 150 + 120 * Math.sin((startAngle * Math.PI) / 180); const endX = 150 + 120 * Math.cos((endAngle * Math.PI) / 180); const endY = 150 + 120 * Math.sin((endAngle * Math.PI) / 180); const largeArcFlag = sectorAngle > 180 ? 1 : 0; return ( <path key={index} d={`M 150 150 L ${startX} ${startY} A 120 120 0 ${largeArcFlag} 1 ${endX} ${endY} Z`} fill={stateColors[state]} style={{ cursor: 'pointer', opacity: 0.8, transition: 'opacity 0.2s' }} onClick={() => handleHourClick(index)} onMouseEnter={(e) => e.target.style.opacity = 1} onMouseLeave={(e) => e.target.style.opacity = 0.8} /> ); })} <text x="150" y="155" textAnchor="middle" fontSize="16" fill="#333">24小时峰谷标注</text> </svg> {/* 状态图例 */} <div style={{ position: 'absolute', bottom: 0, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: '15px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}> <div style={{ width: '15px', height: '15px', backgroundColor: stateColors[0] }}></div> <span>Off-Peak</span> </div> <div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}> <div style={{ width: '15px', height: '15px', backgroundColor: stateColors[1] }}></div> <span>Mid-Peak</span> </div> <div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}> <div style={{ width: '15px', height: '15px', backgroundColor: stateColors[2] }}></div> <span>On-Peak</span> </div> </div> </div> ); }; export default PeakHourPicker;
第三方库简化开发
如果不想从零编写SVG,可以选择支持分段点击的环形时间组件(如react-circular-slider),将24个刻度对应24个时段,自定义点击切换逻辑即可。
Bootstrap + React 实现方案
要贴合Bootstrap UI风格,可结合react-bootstrap组件封装,复用框架主题色:
import { useState } from 'react'; import { Card, Badge } from 'react-bootstrap'; const BootstrapPeakHourPicker = () => { const [hourStates, setHourStates] = useState(Array(24).fill(0)); // 映射Bootstrap主题色 const stateColors = { 0: 'secondary', // off-peak 1: 'warning', // mid-peak 2: 'danger' // on-peak }; const handleHourClick = (index) => { const newStates = [...hourStates]; newStates[index] = (newStates[index] + 1) % 3; setHourStates(newStates); }; const sectorAngle = 360 / 24; return ( <Card className="p-3" style={{ maxWidth: '400px', margin: '0 auto' }}> <Card.Title>24小时峰谷时段设置</Card.Title> <div style={{ position: 'relative', width: '100%', height: '300px' }}> <svg width="100%" height="100%" viewBox="0 0 300 300"> <circle cx="150" cy="150" r="120" fill="#f8f9fa" /> {hourStates.map((state, index) => { const startAngle = index * sectorAngle; const endAngle = startAngle + sectorAngle; const startX = 150 + 120 * Math.cos((startAngle * Math.PI) / 180); const startY = 150 + 120 * Math.sin((startAngle * Math.PI) / 180); const endX = 150 + 120 * Math.cos((endAngle * Math.PI) / 180); const endY = 150 + 120 * Math.sin((endAngle * Math.PI) / 180); const largeArcFlag = sectorAngle > 180 ? 1 : 0; return ( <path key={index} d={`M 150 150 L ${startX} ${startY} A 120 120 0 ${largeArcFlag} 1 ${endX} ${endY} Z`} fill={`var(--bs-${stateColors[state]})`} style={{ cursor: 'pointer', opacity: 0.7, transition: 'opacity 0.2s' }} onClick={() => handleHourClick(index)} onMouseEnter={(e) => e.target.style.opacity = 1} onMouseLeave={(e) => e.target.style.opacity = 0.7} /> ); })} <text x="150" y="155" textAnchor="middle" fontSize="16" fill="#495057">点击切换时段</text> </svg> </div> <div className="mt-3 d-flex justify-content-center gap-3"> <div className="d-flex align-items-center gap-2"> <Badge bg={stateColors[0]}>●</Badge> <span>Off-Peak</span> </div> <div className="d-flex align-items-center gap-2"> <Badge bg={stateColors[1]}>●</Badge> <span>Mid-Peak</span> </div> <div className="d-flex align-items-center gap-2"> <Badge bg={stateColors[2]}>●</Badge> <span>On-Peak</span> </div> </div> </Card> ); }; export default BootstrapPeakHourPicker;
关键注意点
- 调整SVG路径绘制逻辑,确保每个扇形的点击区域精准。
- 用
(current + 1) % 3保证状态在0-2之间循环切换。 - 可根据需求自定义颜色,Bootstrap方案复用主题色更符合UI统一要求。
内容的提问来源于stack exchange,提问作者N TP
相关产品推荐
相关产品推荐

