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

在Next.js(TSX)中用Chart.js创建指定图表:浮动条形图剩余区域填充问题

在Next.js(TSX)中使用Chart.js实现带阴影填充的时间轴条形图

需求描述

希望创建如图所示的图表:
目标图表
已尝试使用浮动条形图,但无法为条形图的剩余区域添加阴影填充,寻求解决方法。

相关数据

const dayData = [
    {
        date: "2021-09-01",
        timesExercised: {
            exercise1: {
                startTime: "2021-09-01T09:00:00",
                endTime: "2021-09-01T09:30:00"
            },
            exercise2: {
                startTime: "2021-09-01T10:00:00",
                endTime: "2021-09-01T10:30:00"
            }
        }
    },
    {
        date: "2021-09-02",
        timesExercised: {
            exercise1: {
                startTime: "2021-09-02T09:00:00",
                endTime: "2021-09-02T09:30:00"
            },
            exercise2: {
                startTime: "2021-09-02T10:00:00",
                endTime: "2021-09-02T10:30:00"
            }
        }
    }
];

解决方法

要实现剩余区域的阴影填充,不能仅依赖浮动条形图,需要结合Chart.js自定义插件和背景填充图层来实现,以下是具体步骤:

1. 安装依赖

确保项目已安装必要包:

npm install chart.js react-chartjs-2 @types/chart.js

2. 数据预处理

将时间转换为一天内的分钟数(0-1440),方便Chart.js的线性轴处理:

// 转换时间字符串为当日分钟数
const timeToMinutes = (timeStr: string) => {
  const date = new Date(timeStr);
  return date.getHours() * 60 + date.getMinutes();
};

// 处理原始数据,提取日期、锻炼时间段、全天时间范围
const processedData = dayData.map(item => {
  const exercises = Object.values(item.timesExercised).map(ex => ({
    start: timeToMinutes(ex.startTime),
    end: timeToMinutes(ex.endTime)
  }));
  return {
    date: item.date,
    exercises,
    fullDay: [0, 1440] // 全天时间范围,用于绘制阴影背景
  };
});

3. 编写自定义阴影填充插件

通过Chart.js的beforeDraw钩子,在条形图下方绘制阴影背景:

const shadowFillPlugin = {
  id: 'shadowFill',
  beforeDraw: (chart: Chart) => {
    const ctx = chart.ctx;
    const { data, scales } = chart;
    
    data.datasets.forEach((dataset) => {
      if (dataset.label !== '背景填充') return;
      
      data.labels?.forEach((_, index) => {
        // 计算每个条形的位置和尺寸
        const barWidth = scales.x.getPixelForValue(index + 0.5) - scales.x.getPixelForValue(index - 0.5);
        const barX = scales.x.getPixelForValue(index);
        const yStart = scales.y.getPixelForValue(dataset.data[index][0]);
        const yEnd = scales.y.getPixelForValue(dataset.data[index][1]);
        
        // 绘制半透明阴影背景
        ctx.save();
        ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
        ctx.fillRect(barX - barWidth / 2, yStart, barWidth, yEnd - yStart);
        ctx.restore();
      });
    });
  }
};

4. 完整TSX组件实现

在Next.js组件中整合代码,渲染图表(App Router需添加'use client'指令):

'use client';

import { Chart, ChartConfiguration } from 'chart.js';
import { Bar } from 'react-chartjs-2';

// 注册自定义插件
Chart.register(shadowFillPlugin);

export default function ExerciseChart() {
  // 数据转换函数
  const timeToMinutes = (timeStr: string) => {
    const date = new Date(timeStr);
    return date.getHours() * 60 + date.getMinutes();
  };

  // 处理数据
  const processedData = dayData.map(item => {
    const exercises = Object.values(item.timesExercised).map(ex => ({
      start: timeToMinutes(ex.startTime),
      end: timeToMinutes(ex.endTime)
    }));
    return {
      date: item.date,
      exercises,
      fullDay: [0, 1440]
    };
  });

  // Chart.js配置
  const chartConfig: ChartConfiguration = {
    type: 'bar',
    data: {
      labels: processedData.map(d => d.date),
      datasets: [
        // 透明背景数据集(用于插件绘制阴影)
        {
          label: '背景填充',
          data: processedData.map(d => d.fullDay),
          backgroundColor: 'transparent',
          borderColor: 'transparent',
          borderWidth: 0,
          barPercentage: 0.8,
          categoryPercentage: 0.8
        },
        // 锻炼时间段条形图(支持多个锻炼项)
        ...processedData[0].exercises.map((_, idx) => ({
          label: `锻炼${idx + 1}`,
          data: processedData.map(d => [d.exercises[idx].start, d.exercises[idx].end]),
          backgroundColor: idx === 0 ? 'rgba(75, 192, 192, 0.6)' : 'rgba(255, 99, 132, 0.6)',
          borderColor: idx === 0 ? 'rgba(75, 192, 192, 1)' : 'rgba(255, 99, 132, 1)',
          borderWidth: 1,
          barPercentage: 0.8,
          categoryPercentage: 0.8
        }))
      ]
    },
    options: {
      indexAxis: 'y', // 横向条形图,如需纵向可删除此配置
      scales: {
        x: {
          type: 'linear',
          title: { display: true, text: '时间' },
          ticks: {
            // 将分钟数转换为HH:mm格式显示
            callback: (value) => {
              const hours = Math.floor(Number(value) / 60);
              const minutes = Number(value) % 60;
              return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
            }
          }
        },
        y: { title: { display: true, text: '日期' } }
      },
      plugins: { legend: { position: 'top' } }
    }
  };

  return <Bar {...chartConfig} />;
}

// 原始数据
const dayData = [
    {
        date: "2021-09-01",
        timesExercised: {
            exercise1: {
                startTime: "2021-09-01T09:00:00",
                endTime: "2021-09-01T09:30:00"
            },
            exercise2: {
                startTime: "2021-09-01T10:00:00",
                endTime: "2021-09-01T10:30:00"
            }
        }
    },
    {
        date: "2021-09-02",
        timesExercised: {
            exercise1: {
                startTime: "2021-09-02T09:00:00",
                endTime: "2021-09-02T09:30:00"
            },
            exercise2: {
                startTime: "2021-09-02T10:00:00",
                endTime: "2021-09-02T10:30:00"
            }
        }
    }
];

关键说明

  • 透明背景数据集仅作为占位,由自定义插件绘制阴影填充,实现剩余区域的阴影效果。
  • 时间转换为分钟数后,线性轴可以更精准地展示时间范围。
  • 若目标图表为纵向条形图,删除indexAxis: 'y'配置即可。

内容的提问来源于stack exchange,提问作者Abdulmoiz Kamran

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 09:05:43