You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现交互式峰谷时段标注环形图表?求React/Bootstrap相关框架

实现24小时分段交互式峰谷时段标注的方案

React 实现方案

自定义SVG组件(推荐)

不需要依赖复杂第三方库,直接用SVG绘制24个扇形分段,结合React状态管理实现点击切换逻辑:

  1. 状态设计:用数组hourStates存储24个时段的状态,每个元素取值0(off-peak)、1(mid-peak)、2(on-peak),初始全为0。
  2. 切换逻辑:点击对应扇形时,将该位置状态加1后取模3,实现循环切换。
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:00:58