chart.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import 'dart:math';
  2. import 'dart:ui' as ui;
  3. import 'dart:ui';
  4. import 'package:flutter/cupertino.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:sport/bean/sport_detail.dart';
  7. import 'package:sport/services/Converter.dart';
  8. const WEEK = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
  9. class ChartItem {
  10. final String createdAt;
  11. final int value;
  12. ChartItem(this.createdAt, this.value);
  13. }
  14. class Chart extends CustomPainter {
  15. final Paint _paint = Paint()
  16. ..color = const Color(0xffDCDCDC)
  17. ..strokeWidth = 0.5
  18. ..isAntiAlias = true;
  19. final Paint _linePaint = Paint()
  20. ..color = const Color(0xffFFC400)
  21. ..strokeWidth = 2
  22. ..isAntiAlias = true
  23. ..style = PaintingStyle.stroke;
  24. static const Color _maxColor = Color(0xffFF5B1D);
  25. final Paint _maxPaint = Paint()
  26. ..color = _maxColor
  27. ..strokeWidth = 0.5
  28. ..isAntiAlias = true;
  29. final ParagraphStyle _labelStyle = ParagraphStyle(
  30. textAlign: TextAlign.left,
  31. fontSize: 8,
  32. );
  33. final ParagraphStyle _valueStyle = ParagraphStyle(
  34. textAlign: TextAlign.right,
  35. fontSize: 8,
  36. );
  37. double _zero = 0;
  38. double _paddingLeft = 30;
  39. double _paddingRight = 10;
  40. double _labelHeight = 30;
  41. int _valueSize = 7;
  42. int _valueSplit = 200;
  43. int type;
  44. List<ChartItem> records;
  45. DateTime dateTime;
  46. Map<String, num> values;
  47. bool drawMax = false;
  48. String unit = "kal";
  49. double _max = 750;
  50. Chart({this.type, this.records, this.dateTime, this.drawMax, this.unit});
  51. void initData({double maxValue = 750, int valueSize = 7, int valueSplit = 200}) {
  52. _valueSize = valueSize;
  53. _valueSplit = valueSplit;
  54. values = {};
  55. var records = this.records ?? [];
  56. if (this.type == 0) {
  57. records.forEach((element) {
  58. var t = DateTime.parse(element.createdAt);
  59. values.update("${max(6, t.hour) - 5}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value));
  60. });
  61. for (int i = 1; i <= 24; i++) {
  62. values.putIfAbsent("$i", () => 0.0);
  63. }
  64. } else if (this.type == 1) {
  65. records.forEach((element) {
  66. var t = DateTime.parse(element.createdAt);
  67. values.update("${t.weekday == 0 ? 7 : t.weekday}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value));
  68. });
  69. for (int i = 1; i <= 7; i++) {
  70. values.putIfAbsent("$i", () => 0.0);
  71. }
  72. } else if (this.type == 2) {
  73. records.forEach((element) {
  74. var t = DateTime.parse(element.createdAt);
  75. values.update("${t.day}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value));
  76. });
  77. for (int i = 1; i <= 31; i++) {
  78. values.putIfAbsent("$i", () => 0.0);
  79. }
  80. } else if (this.type == 3) {
  81. records.forEach((element) {
  82. values.update("${element.createdAt}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value));
  83. });
  84. for (int i = 1; i <= 12; i++) {
  85. values.putIfAbsent("$i", () => 0.0);
  86. }
  87. }
  88. values.values.forEach((element) {
  89. _max = max(maxValue, Converter.toDouble(element));
  90. });
  91. double diff = _max % _valueSplit;
  92. _max = max(maxValue, _max + diff);
  93. // print("$values --- $_max $maxValue $diff");
  94. }
  95. @override
  96. void paint(Canvas canvas, Size size) {
  97. double zero = _zero = size.height - _labelHeight;
  98. // draw 值 行数
  99. int valuePadding = 10;
  100. canvas.drawLine(Offset(_paddingLeft, zero), Offset(size.width, zero), _paint);
  101. ParagraphBuilder pb = ParagraphBuilder(_valueStyle)
  102. ..pushStyle(ui.TextStyle(color: Color(0xff999999)))
  103. ..addText("0");
  104. ParagraphConstraints constraints = ParagraphConstraints(width: _paddingLeft - valuePadding);
  105. Paragraph paragraph = pb.build()..layout(constraints);
  106. paragraph.computeLineMetrics().forEach((element) {
  107. canvas.drawParagraph(paragraph, Offset(0, zero - element.baseline / 2));
  108. });
  109. canvas.drawLine(Offset(_paddingLeft, zero), Offset(_paddingLeft, 0), _paint);
  110. canvas.drawLine(Offset(size.width, zero), Offset(size.width, 0), _paint);
  111. double valueSpace = (size.height - _labelHeight) / _valueSize;
  112. for (var i = 0; i < _valueSize; i++) {
  113. canvas.drawLine(Offset(_paddingLeft, valueSpace * i), Offset(size.width, valueSpace * i), _paint);
  114. var value = (_valueSize - i) * _max ~/ _valueSize;
  115. ParagraphBuilder pb = ParagraphBuilder(_valueStyle)
  116. ..pushStyle(ui.TextStyle(color: Color(0xff999999)))
  117. ..addText(value < 1000 ? "${value}" : "${(value / 1000).toStringAsFixed(1)}k");
  118. ParagraphConstraints constraints = ParagraphConstraints(width: _paddingLeft - valuePadding);
  119. Paragraph paragraph = pb.build()..layout(constraints);
  120. paragraph.computeLineMetrics().forEach((element) {
  121. canvas.drawParagraph(paragraph, Offset(0, valueSpace * i - element.baseline / 2));
  122. });
  123. // print("${value / 1000} ${_max} ${(_valueSize - i) * _max ~/ _valueSize}");
  124. }
  125. // draw 数据列
  126. double left = _paddingLeft + 10;
  127. double width = size.width - _paddingRight - left; // 柱子的有效空间
  128. double valueStroke = 20; // 柱子宽度
  129. List<String> valueLabel = []; // 柱子数量
  130. int scaleCount = 0; // 刻度数量
  131. int scaleWeight = 3;
  132. if (this.type == 0) {
  133. // 日,按时间
  134. scaleWeight = 2;
  135. valueLabel = List.generate(10, (index) => "${6 + index * scaleWeight}:00");
  136. valueStroke = width / valueLabel.length / scaleWeight - 5;
  137. scaleCount = 18;
  138. } else if (this.type == 1) {
  139. left += 20;
  140. width -= 40;
  141. scaleWeight = 1;
  142. valueLabel = List.generate(7, (index) => WEEK[index]);
  143. valueStroke = 12;
  144. scaleCount = 6;
  145. } else if (this.type == 2) {
  146. DateTime now = this.dateTime;
  147. // DateTime now = DateTime(2019,2);
  148. DateTime startTime = DateTime.parse('${now.year}-${now.month < 10 ? '0${now.month}' : now.month}-01');
  149. DateTime endTime = DateTime.parse('${now.year}-${now.month + 1 < 10 ? '0${now.month + 1}' : now.month + 1}-01');
  150. int diffDay = endTime.difference(startTime).inDays;
  151. // List<int> days = [1, 8, 15, 22, diffDay == 28 ? 28:29];
  152. scaleCount = diffDay - 1;
  153. scaleWeight = 2;
  154. // valueLabel = days.map((e) => '$e/${now.month}').toList();
  155. valueLabel = List.generate(diffDay ~/ scaleWeight + (scaleCount % scaleWeight == 0 ? 1 : 0), (index) => "${index * 2 + 1}");
  156. valueStroke = 5;
  157. } else if (this.type == 3) {
  158. valueLabel = List.generate(12, (index) => '${index + 1}');
  159. valueStroke = 12;
  160. scaleCount = 12 - 1;
  161. scaleWeight = 1;
  162. }
  163. int labelSize = valueLabel.length;
  164. double labelSpace = (width) / labelSize;
  165. double scaleWidth = (width) / scaleCount;
  166. // if (this.type < 2) {
  167. //draw 刻度
  168. for (var i = 0; i <= scaleCount; i++) {
  169. canvas.drawLine(Offset(left + scaleWidth * i, zero), Offset(left + scaleWidth * i, zero - 3), _paint);
  170. }
  171. // }
  172. for (var i = 0; i < labelSize; i++) {
  173. String label = valueLabel[i];
  174. ParagraphBuilder pb = ParagraphBuilder(_labelStyle)
  175. ..pushStyle(ui.TextStyle(color: Color(0xff999999)))
  176. ..addText(label);
  177. ParagraphConstraints constraints = ParagraphConstraints(width: labelSpace);
  178. Paragraph paragraph = pb.build()..layout(constraints);
  179. paragraph.computeLineMetrics().forEach((element) {
  180. canvas.drawParagraph(paragraph, Offset(left + scaleWidth * scaleWeight * i - element.width / 2, zero + 5));
  181. });
  182. //
  183. // double l = left + scaleWidth * scaleWeight * i - valueStroke / 2;
  184. // Rect rect = Rect.fromLTRB(l, zero, l + valueStroke, 0);
  185. // print("value draw $rect");
  186. // Paint valuePaint = Paint()
  187. // ..shader = LinearGradient(
  188. // begin: Alignment.bottomCenter,
  189. // end: Alignment.topCenter,
  190. // colors: <Color>[Color(0xffFF9100), Color(0xffFFE600)],
  191. // ).createShader(rect);
  192. // canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(100)), valuePaint);
  193. }
  194. double maxVal = values.values.reduce((value, element) => max(value, element).toDouble());
  195. // if (this.type < 2) {
  196. for (var i = 0; i <= scaleCount; i++) {
  197. String key = '${i + 1}';
  198. // print("$key ${values[key]} 11111111111111 $scaleCount");
  199. if (!values.containsKey(key)) continue;
  200. num value = values[key];
  201. double l = left + scaleWidth * i - valueStroke / 2;
  202. Rect rect = Rect.fromLTRB(l, zero, l + valueStroke, calValue(value));
  203. Paint valuePaint = Paint()
  204. ..shader = LinearGradient(
  205. begin: Alignment.bottomCenter,
  206. end: Alignment.topCenter,
  207. colors: <Color>[Color(0xffFF9100), Color(0xffFFE600)],
  208. ).createShader(rect);
  209. canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(100)), valuePaint);
  210. if (this.drawMax) {
  211. if (maxVal <= value) {
  212. if (maxVal > 0) {
  213. double offsetY = calValue(maxVal);
  214. double offsetX = _paddingLeft;
  215. while (offsetX < size.width) {
  216. canvas.drawLine(Offset(offsetX, offsetY), Offset(min(offsetX + 3, size.width), offsetY), _maxPaint);
  217. offsetX += 5;
  218. }
  219. ParagraphBuilder unit = ParagraphBuilder(_labelStyle)
  220. ..pushStyle(ui.TextStyle(color: _maxColor))
  221. ..addText("${value.round()}${this.unit}");
  222. ParagraphConstraints pc = ParagraphConstraints(width: size.width);
  223. paragraph = unit.build()..layout(pc);
  224. paragraph.computeLineMetrics().forEach((element) {
  225. var x = l + valueStroke / 2 - element.width / 2;
  226. canvas.drawParagraph(paragraph, Offset(x, offsetY + (offsetY < 1 ? element.baseline : -element.baseline) - 10));
  227. x = l + valueStroke / 2 ;
  228. canvas.drawPath(Path()..moveTo(x, offsetY - 4)..lineTo(x - 3, offsetY - 7)..lineTo(x + 3, offsetY - 7)..close(), _maxPaint);
  229. });
  230. }
  231. }
  232. }
  233. }
  234. // } else {
  235. // Path path = Path();
  236. // path.moveTo(_paddingLeft, calValue(values['1']));
  237. // Path area = Path()
  238. // ..moveTo(_paddingLeft, zero)
  239. // ..lineTo(_paddingLeft, calValue(values['1']));
  240. // for (var i = 1; i <= scaleCount; i++) {
  241. // path.lineTo(left + scaleWidth * i, calValue(values['$i']));
  242. // area.lineTo(left + scaleWidth * i, calValue(values['$i']));
  243. // }
  244. // area.lineTo(size.width, zero);
  245. // area.close();
  246. //
  247. // canvas.save();
  248. // canvas.clipPath(area);
  249. // Rect rect = Rect.fromLTRB(_paddingLeft, zero, size.width, calValue(_max));
  250. // Paint valuePaint = Paint()
  251. // ..shader = LinearGradient(
  252. // begin: Alignment.bottomCenter,
  253. // end: Alignment.topCenter,
  254. // colors: <Color>[Color(0xffFFC400), Color(0x50FFFFFF)],
  255. // ).createShader(rect);
  256. // canvas.drawRect(rect, valuePaint);
  257. // canvas.restore();
  258. //
  259. // canvas.drawPath(path, _linePaint);
  260. // }
  261. }
  262. double calValue(num value) {
  263. if (value == null) return _zero;
  264. return min(_zero, _zero - _zero * value / _max);
  265. }
  266. @override
  267. bool shouldRepaint(CustomPainter oldDelegate) {
  268. return false;
  269. }
  270. }