DateValueSingleLineChart.dart 7.6 KB

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