在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
相关产品推荐
相关产品推荐

