Files
ai_english/client/lib/features/home/widgets/learning_trend_chart.dart
2025-11-17 14:09:17 +08:00

157 lines
4.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
/// 学习趋势图表
class LearningTrendChart extends StatelessWidget {
final List<Map<String, dynamic>> 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<FlSpot> _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();
}
}