254 lines
6.6 KiB
Dart
254 lines
6.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:food_hub_app/models/stats.dart';
|
||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||
|
||
Widget lineChart({
|
||
required BuildContext context,
|
||
required String xAxisName,
|
||
required String yAxisName,
|
||
required String unit,
|
||
required List<ChartData> data,
|
||
}) {
|
||
return SfCartesianChart(
|
||
// 图表标题
|
||
// title: ChartTitle(text: '2023年上半年销售额(万元)'),
|
||
|
||
// X轴配置(类别轴)
|
||
primaryXAxis: CategoryAxis(majorGridLines: MajorGridLines(width: 0)),
|
||
|
||
// Y轴配置(数值轴)
|
||
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||
|
||
// 启用图例
|
||
// legend: const Legend(isVisible: true),
|
||
|
||
// 启用交互提示(点击数据点显示详情)
|
||
tooltipBehavior: TooltipBehavior(
|
||
enable: true,
|
||
format: 'point.x: point.y $unit',
|
||
),
|
||
|
||
// 折线图数据系列
|
||
series: [
|
||
LineSeries<ChartData, String>(
|
||
dataSource: data,
|
||
// X轴数据映射
|
||
xValueMapper: (ChartData chart, _) => chart.name,
|
||
// Y轴数据映射
|
||
yValueMapper: (ChartData chart, _) => chart.value,
|
||
// 线条颜色
|
||
color: Theme.of(context).primaryColor,
|
||
// 线条宽度
|
||
width: 3,
|
||
|
||
// 数据点样式
|
||
markerSettings: const MarkerSettings(
|
||
isVisible: true,
|
||
color: Colors.white,
|
||
shape: DataMarkerType.circle,
|
||
height: 6,
|
||
width: 6,
|
||
),
|
||
|
||
// 折线名称(会显示在图例中)
|
||
name: yAxisName,
|
||
|
||
// 启用数据标签(直接显示数值)
|
||
dataLabelSettings: const DataLabelSettings(
|
||
isVisible: true,
|
||
color: Colors.white,
|
||
opacity: 0,
|
||
),
|
||
|
||
// 动画效果
|
||
animationDuration: 2000, // 动画时长(毫秒)
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget barChart({
|
||
required BuildContext context,
|
||
required String xAxisName,
|
||
required String yAxisName,
|
||
required String unit,
|
||
required List<ChartData> data,
|
||
}) {
|
||
return SfCartesianChart(
|
||
// X轴配置(类别轴)
|
||
primaryXAxis: CategoryAxis(
|
||
majorGridLines: MajorGridLines(width: 0),
|
||
title: AxisTitle(text: xAxisName),
|
||
),
|
||
|
||
// Y轴配置(数值轴)
|
||
primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||
|
||
// 启用交互提示
|
||
tooltipBehavior: TooltipBehavior(
|
||
enable: true,
|
||
format: 'point.x: point.y $unit',
|
||
),
|
||
|
||
// 柱状图数据系列
|
||
series: [
|
||
ColumnSeries<ChartData, String>(
|
||
dataSource: data,
|
||
// X轴数据映射
|
||
xValueMapper: (ChartData chart, _) => chart.name,
|
||
// Y轴数据映射
|
||
yValueMapper: (ChartData chart, _) => chart.value,
|
||
|
||
// 名称
|
||
name: yAxisName,
|
||
|
||
// 柱子颜色
|
||
color: Theme.of(context).primaryColor,
|
||
|
||
// 柱子宽度(0-1之间,1表示占满类别间隔)
|
||
width: 0.6,
|
||
|
||
// 柱子边框
|
||
borderWidth: 1,
|
||
borderColor: Colors.black12,
|
||
|
||
// 数据标签
|
||
dataLabelSettings: const DataLabelSettings(
|
||
isVisible: true,
|
||
color: Colors.white,
|
||
opacity: 0,
|
||
alignment: ChartAlignment.center,
|
||
),
|
||
|
||
// 动画效果
|
||
animationDuration: 2000,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget pieChart({
|
||
required BuildContext context,
|
||
required String unit,
|
||
required List<ChartData> data,
|
||
}) {
|
||
// 计算 value 的总和
|
||
double sumValue = data.fold(0.0, (sum, item) => sum + item.value);
|
||
|
||
return SfCircularChart(
|
||
// 饼图标题
|
||
// title: ChartTitle(text: '菜谱类别占比分布'),
|
||
|
||
// 启用图例
|
||
legend: const Legend(isVisible: true, position: LegendPosition.right),
|
||
|
||
// 启用交互提示(点击扇区显示详情)
|
||
tooltipBehavior: TooltipBehavior(
|
||
enable: true,
|
||
format: 'point.x: point.y $unit',
|
||
),
|
||
|
||
// 饼图系列配置
|
||
series: [
|
||
PieSeries<ChartData, String>(
|
||
dataSource: data,
|
||
// 类别映射(饼图扇区名称)
|
||
xValueMapper: (ChartData data, _) => data.name,
|
||
// 数值映射(扇区大小占比)
|
||
yValueMapper: (ChartData data, _) => data.value,
|
||
|
||
// 扇区半径(0-1之间,1表示充满容器)
|
||
// radius: '50%',
|
||
|
||
// 启用扇区分离效果
|
||
explode: true,
|
||
// 指定分离的扇区索引(这里分离第一个扇区)
|
||
explodeIndex: 0,
|
||
// 分离距离
|
||
explodeOffset: '5%',
|
||
|
||
dataLabelMapper: (ChartData data, _) {
|
||
final percentage = (data.value / sumValue * 100).toStringAsFixed(0);
|
||
return '$percentage%';
|
||
},
|
||
|
||
// 数据标签(显示在扇区上的文本)
|
||
dataLabelSettings: DataLabelSettings(isVisible: true),
|
||
|
||
// 动画效果
|
||
animationDuration: 2000,
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget buildChartTitle({required BuildContext context, required String title}) {
|
||
return Row(
|
||
children: [
|
||
Icon(Icons.insert_chart, color: Theme.of(context).colorScheme.primary),
|
||
Text(title),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget buildChartDivider({required BuildContext context}) {
|
||
return Divider(
|
||
height: 1,
|
||
thickness: 1,
|
||
color: Theme.of(context).colorScheme.primary,
|
||
indent: 0,
|
||
endIndent: 0,
|
||
);
|
||
}
|
||
|
||
Color _getRankColor(int index) {
|
||
switch (index) {
|
||
case 0: // 第1名
|
||
return Colors.amber; // 金色
|
||
case 1: // 第2名
|
||
return Colors.grey; // 银色
|
||
case 2: // 第3名
|
||
return Colors.orange[700]!; // 铜色
|
||
default:
|
||
return Colors.black; // 普通颜色
|
||
}
|
||
}
|
||
|
||
Widget rankChart({required String unit, required List<ChartData> data}) {
|
||
return ListView.builder(
|
||
itemCount: data.length,
|
||
itemBuilder: (context, index) {
|
||
final item = data[index];
|
||
final rank = index + 1;
|
||
return SizedBox(
|
||
height: 35,
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Container(
|
||
width: 25,
|
||
alignment: Alignment.center,
|
||
child: Text(
|
||
'$rank.',
|
||
style: TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
color: _getRankColor(index),
|
||
),
|
||
),
|
||
),
|
||
Text(item.name, style: TextStyle(color: _getRankColor(index))),
|
||
],
|
||
),
|
||
Text(
|
||
'${item.value.toStringAsFixed(0)} $unit',
|
||
style: TextStyle(color: _getRankColor(index)),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
);
|
||
}
|