feat:增加统计图表模块
This commit is contained in:
297
lib/widgets/chart.dart
Normal file
297
lib/widgets/chart.dart
Normal file
@@ -0,0 +1,297 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
|
||||
class ChartData {
|
||||
final String name;
|
||||
final double value;
|
||||
|
||||
ChartData({required this.name, required this.value});
|
||||
}
|
||||
|
||||
Widget buildChartTitle(BuildContext context, String title) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(Icons.insert_chart, color: Theme.of(context).colorScheme.primary),
|
||||
Text(title),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildChartDivider(BuildContext context) {
|
||||
return Divider(
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
indent: 0,
|
||||
endIndent: 0,
|
||||
);
|
||||
}
|
||||
|
||||
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: Legend(isVisible: true, position: LegendPosition.top),
|
||||
|
||||
// 启用交互提示(点击数据点显示详情)
|
||||
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).colorScheme.primary,
|
||||
// 线条宽度
|
||||
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).colorScheme.primary,
|
||||
|
||||
// 柱子宽度(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 doubleBarChart({
|
||||
required BuildContext context,
|
||||
required String xAxisName,
|
||||
required String yAxisName,
|
||||
required String unit,
|
||||
required List<ChartData> data1,
|
||||
required List<ChartData> data2,
|
||||
required String series1Name,
|
||||
required String series2Name,
|
||||
}) {
|
||||
return SfCartesianChart(
|
||||
// X轴配置(类别轴)
|
||||
primaryXAxis: CategoryAxis(
|
||||
majorGridLines: const MajorGridLines(width: 0),
|
||||
title: AxisTitle(text: xAxisName),
|
||||
),
|
||||
|
||||
// Y轴配置(数值轴)
|
||||
// primaryYAxis: NumericAxis(title: AxisTitle(text: '$yAxisName($unit)')),
|
||||
|
||||
// 图例配置
|
||||
legend: Legend(
|
||||
isVisible: true,
|
||||
position: LegendPosition.top,
|
||||
overflowMode: LegendItemOverflowMode.wrap,
|
||||
),
|
||||
|
||||
// 启用交互提示
|
||||
tooltipBehavior: TooltipBehavior(
|
||||
enable: true,
|
||||
format: 'series.name: point.y $unit',
|
||||
),
|
||||
|
||||
// 双柱状图数据系列
|
||||
series: <ColumnSeries<ChartData, String>>[
|
||||
ColumnSeries<ChartData, String>(
|
||||
dataSource: data1,
|
||||
// X轴数据映射
|
||||
xValueMapper: (ChartData chart, _) => chart.name,
|
||||
// Y轴数据映射
|
||||
yValueMapper: (ChartData chart, _) => chart.value,
|
||||
// 系列名称
|
||||
name: series1Name,
|
||||
// 柱子颜色
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
// 柱子宽度
|
||||
width: 0.3,
|
||||
// 柱子边框
|
||||
borderWidth: 1,
|
||||
borderColor: Colors.black12,
|
||||
// 数据标签
|
||||
dataLabelSettings: const DataLabelSettings(
|
||||
isVisible: true,
|
||||
color: Colors.white,
|
||||
opacity: 0,
|
||||
alignment: ChartAlignment.center,
|
||||
),
|
||||
// 动画效果
|
||||
animationDuration: 2000,
|
||||
),
|
||||
ColumnSeries<ChartData, String>(
|
||||
dataSource: data2,
|
||||
// X轴数据映射
|
||||
xValueMapper: (ChartData chart, _) => chart.name,
|
||||
// Y轴数据映射
|
||||
yValueMapper: (ChartData chart, _) => chart.value,
|
||||
// 系列名称
|
||||
name: series2Name,
|
||||
// 柱子颜色
|
||||
color: Theme.of(context).colorScheme.inversePrimary,
|
||||
// 柱子宽度
|
||||
width: 0.3,
|
||||
// 柱子边框
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user