import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; /// 学习趋势图表 class LearningTrendChart extends StatelessWidget { final List> weeklyData; const LearningTrendChart({ super.key, required this.weeklyData, }); @override Widget build(BuildContext context) { if (weeklyData.isEmpty) { return const Center( child: Text( '暂无学习数据', style: TextStyle(color: Colors.grey), ), ); } return Container( height: 200, padding: const EdgeInsets.all(16), child: LineChart( LineChartData( gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: 20, getDrawingHorizontalLine: (value) { return FlLine( color: Colors.grey.withOpacity(0.1), strokeWidth: 1, ); }, ), titlesData: FlTitlesData( show: true, rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, interval: 1, getTitlesWidget: (value, meta) { final index = value.toInt(); if (index < 0 || index >= weeklyData.length) { return const Text(''); } final date = DateTime.parse(weeklyData[index]['date']); final dayLabel = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'][date.weekday - 1]; return Text( dayLabel, style: const TextStyle( color: Colors.grey, fontSize: 10, ), ); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 20, reservedSize: 35, getTitlesWidget: (value, meta) { return Text( value.toInt().toString(), style: const TextStyle( color: Colors.grey, fontSize: 10, ), ); }, ), ), ), borderData: FlBorderData( show: true, border: Border( bottom: BorderSide(color: Colors.grey.withOpacity(0.2)), left: BorderSide(color: Colors.grey.withOpacity(0.2)), ), ), minX: 0, maxX: (weeklyData.length - 1).toDouble(), minY: 0, maxY: _getMaxY(), lineBarsData: [ LineChartBarData( spots: _generateSpots(), isCurved: true, gradient: const LinearGradient( colors: [Color(0xFF2196F3), Color(0xFF1976D2)], ), barWidth: 3, isStrokeCapRound: true, dotData: FlDotData( show: true, getDotPainter: (spot, percent, barData, index) { return FlDotCirclePainter( radius: 4, color: Colors.white, strokeWidth: 2, strokeColor: const Color(0xFF2196F3), ); }, ), belowBarData: BarAreaData( show: true, gradient: LinearGradient( colors: [ const Color(0xFF2196F3).withOpacity(0.2), const Color(0xFF2196F3).withOpacity(0.05), ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ], ), ), ); } List _generateSpots() { return List.generate( weeklyData.length, (index) { final wordsStudied = (weeklyData[index]['words_studied'] ?? 0) as int; return FlSpot(index.toDouble(), wordsStudied.toDouble()); }, ); } double _getMaxY() { if (weeklyData.isEmpty) return 100; final maxWords = weeklyData.map((data) => (data['words_studied'] ?? 0) as int).reduce((a, b) => a > b ? a : b); // 向上取整到最近的10的倍数,并加20作为上边距 return ((maxWords / 10).ceil() * 10 + 20).toDouble(); } }