DateValueMultiLineChart.dart 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. import 'package:fl_chart/fl_chart.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_habit/common/I18N.dart';
  4. import 'package:flutter_habit/provider/ThemeProvider.dart';
  5. import 'package:flutter_habit/common/utils/ConvertUtils.dart';
  6. import 'package:provider/provider.dart';
  7. class DateValueMultiLineChart extends StatelessWidget {
  8. final Map<String, List<FlSpot>> lineNameSportsMap;
  9. final int? size;
  10. DateValueMultiLineChart({
  11. required this.lineNameSportsMap,
  12. this.size,
  13. });
  14. List<LineChartBarData> getLineChartBarData(BuildContext context) {
  15. int i = 0;
  16. List<LineChartBarData> out = lineNameSportsMap.values.map((sports) {
  17. LineChartBarData lineChartBarData = LineChartBarData(
  18. spots: sports,
  19. // 圆滑
  20. isCurved: size! > 7,
  21. // 线条颜色
  22. color:
  23. Provider.of<ThemeProvider>(context, listen: false).otherColors[i]
  24. ,
  25. // 线条宽度
  26. barWidth: 3,
  27. // 起点和终点是否圆滑
  28. isStrokeCapRound: true,
  29. // 点配置
  30. dotData: FlDotData(
  31. show: size! < 30,
  32. // getDotColor: (spot, percent, barData) =>
  33. // Provider.of<ThemeProvider>(context, listen: false).otherColors[i],
  34. // dotSize: 3,
  35. ),
  36. belowBarData: BarAreaData(
  37. show: false,
  38. color:
  39. Theme.of(context).colorScheme.secondary.withOpacity(0.1 / (i + 1))
  40. ,
  41. ),
  42. );
  43. i = (i + 1) % (themeColors.length - 1);
  44. return lineChartBarData;
  45. }).toList();
  46. return out;
  47. }
  48. @override
  49. Widget build(BuildContext context) {
  50. double? maxY, minY, maxX, minX, xInterval, yInterval;
  51. if (lineNameSportsMap.values.first.isNotEmpty) {
  52. double now =
  53. ConvertUtils.localDaysSinceEpoch(DateTime.now()).floorToDouble();
  54. maxX = now;
  55. minX = now - size!;
  56. maxY = lineNameSportsMap.values.first.first.y;
  57. minY = lineNameSportsMap.values.first.first.y;
  58. lineNameSportsMap.values.forEach((sports) {
  59. sports.forEach((i) {
  60. if (i.y < minY!) {
  61. minY = i.y;
  62. }
  63. if (i.y > maxY!) {
  64. maxY = i.y;
  65. }
  66. });
  67. });
  68. if (maxY == maxY) {
  69. maxY = maxY! + 10;
  70. minY = minY! - 10;
  71. } else {
  72. double height = maxY! - minY!;
  73. maxY = maxY! + height;
  74. minY = minY! - height;
  75. }
  76. switch (size) {
  77. case 7:
  78. xInterval = 1;
  79. break;
  80. case 30:
  81. xInterval = 5;
  82. break;
  83. case 90:
  84. xInterval = 18;
  85. break;
  86. default:
  87. xInterval = 1;
  88. break;
  89. }
  90. yInterval = ((maxY! - minY!) / 6);
  91. }
  92. int i = 0;
  93. return lineNameSportsMap.values.first.isEmpty
  94. ? Container(
  95. child: Column(
  96. children: <Widget>[
  97. Text(
  98. I18N.of("无数据"),
  99. style: Theme.of(context)
  100. .textTheme
  101. .displaySmall!
  102. .copyWith(color: Theme.of(context).colorScheme.secondary),
  103. ),
  104. Divider(),
  105. Row(
  106. mainAxisAlignment: MainAxisAlignment.end,
  107. children: <Widget>[
  108. Text(
  109. I18N.of("添加数据后会展示对应图表"),
  110. style: Theme.of(context).textTheme.bodySmall,
  111. ),
  112. ],
  113. ),
  114. ],
  115. ))
  116. : Column(
  117. crossAxisAlignment: CrossAxisAlignment.stretch,
  118. children: <Widget>[
  119. Row(
  120. mainAxisAlignment: MainAxisAlignment.end,
  121. children: lineNameSportsMap.keys.map((s) {
  122. // 右显数据
  123. Widget w = Text(
  124. "$s — ",
  125. style: TextStyle(
  126. color:
  127. Provider.of<ThemeProvider>(context, listen: false)
  128. .otherColors[i]),
  129. );
  130. i = (i + 1) % (themeColors.length - 1);
  131. return w;
  132. }).toList(),
  133. ),
  134. Container(
  135. height: 250,
  136. padding: EdgeInsets.only(top: 20, left: 10, right: 20),
  137. child: LineChart(
  138. LineChartData(
  139. // clipToBorder: true,
  140. // 边框信息
  141. borderData: FlBorderData(
  142. show: true,
  143. border: Border.all(
  144. color: Theme.of(context).colorScheme.secondary,
  145. width: 1,
  146. ),
  147. ),
  148. backgroundColor:
  149. Theme.of(context)
  150. .colorScheme
  151. .secondary
  152. .withOpacity(0.03),
  153. gridData: FlGridData(
  154. show: true,
  155. drawVerticalLine: true,
  156. drawHorizontalLine: false,
  157. // 从左到右每隔几个整数数据画条竖线
  158. verticalInterval: xInterval,
  159. // 横向网格线
  160. getDrawingHorizontalLine: (value) {
  161. return FlLine(
  162. color: Theme.of(context).colorScheme.secondary,
  163. strokeWidth: 0.5,
  164. );
  165. },
  166. horizontalInterval: yInterval,
  167. // 纵向网格线
  168. getDrawingVerticalLine: (value) {
  169. return FlLine(
  170. color: Theme.of(context).colorScheme.secondary,
  171. strokeWidth: 1,
  172. );
  173. },
  174. ),
  175. // 点击响应信息
  176. lineTouchData: LineTouchData(
  177. touchTooltipData: LineTouchTooltipData(
  178. tooltipBgColor: Theme.of(context)
  179. .colorScheme
  180. .secondary
  181. .withOpacity(0.2)
  182. .withAlpha(100),
  183. fitInsideHorizontally: true,
  184. ),
  185. ),
  186. titlesData: FlTitlesData(
  187. show: true,
  188. // 下方文字
  189. bottomTitles: AxisTitles(
  190. // 每隔几个显示一个底部标签
  191. // interval: xInterval,
  192. // showTitles: true,
  193. // 文字与图表上边界距离
  194. // margin: 8,
  195. // 文字预留空间
  196. // reservedSize: 22,
  197. // textStyle: Theme.of(context).textTheme.bodyMedium,
  198. // getTitles: (value) {
  199. // DateTime dateTime =
  200. // ConvertUtils.dateTimeOfLocalDaysSinceEpoch(value);
  201. // return "${dateTime.month}-${dateTime.day}";
  202. // },
  203. ),
  204. leftTitles: AxisTitles(
  205. // 每隔几个显示一个左侧标签
  206. // interval: yInterval,
  207. // showTitles: true,
  208. // 文字与图表左边界距离
  209. // margin: 8,
  210. // 文字预留空间
  211. // reservedSize: ConvertUtils.fixedDouble(maxY, 1).toString().length * 6.5,
  212. // textStyle: Theme.of(context).textTheme.bodyMedium,
  213. // getTitles: (value) {
  214. // return ConvertUtils.fixedDouble(value, 1).toString();
  215. // },
  216. ),
  217. ),
  218. // 各轴显示的最值 (不设置则缩放)
  219. minX: minX,
  220. maxX: maxX,
  221. maxY: maxY,
  222. minY: minY,
  223. // 数据
  224. lineBarsData: getLineChartBarData(context),
  225. ),
  226. ),
  227. )
  228. ],
  229. );
  230. }
  231. }