import 'dart:math'; import 'dart:ui' as ui; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:sport/bean/sport_detail.dart'; import 'package:sport/services/Converter.dart'; const WEEK = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]; class ChartItem { final String createdAt; final int value; ChartItem(this.createdAt, this.value); } class Chart extends CustomPainter { final Paint _paint = Paint() ..color = const Color(0xffDCDCDC) ..strokeWidth = 0.5 ..isAntiAlias = true; final Paint _linePaint = Paint() ..color = const Color(0xffFFC400) ..strokeWidth = 2 ..isAntiAlias = true ..style = PaintingStyle.stroke; static const Color _maxColor = Color(0xffFF5B1D); final Paint _maxPaint = Paint() ..color = _maxColor ..strokeWidth = 0.5 ..isAntiAlias = true; final ParagraphStyle _labelStyle = ParagraphStyle( textAlign: TextAlign.left, fontSize: 8, ); final ParagraphStyle _valueStyle = ParagraphStyle( textAlign: TextAlign.right, fontSize: 8, ); double _zero = 0; double _paddingLeft = 35; double _paddingRight = 10; double _labelHeight = 30; int _valueSize = 7; int _valueSplit = 200; int type; List records; DateTime dateTime; Map values; bool drawMax = false; String unit = "kal"; double _max = 750; Chart({this.type, this.records, this.dateTime, this.drawMax, this.unit}); void initData({double maxValue = 750, int valueSize = 7, int valueSplit = 200}) { _valueSize = valueSize; _valueSplit = valueSplit; values = {}; var records = this.records ?? []; if (this.type == 0) { records.forEach((element) { var t = DateTime.parse(element.createdAt); values.update("${max(6, t.hour) - 5}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value)); }); for (int i = 1; i <= 24; i++) { values.putIfAbsent("$i", () => 0.0); } } else if (this.type == 1) { records.forEach((element) { var t = DateTime.parse(element.createdAt); values.update("${t.weekday == 0 ? 7 : t.weekday}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value)); }); for (int i = 1; i <= 7; i++) { values.putIfAbsent("$i", () => 0.0); } } else if (this.type == 2) { records.forEach((element) { var t = DateTime.parse(element.createdAt); values.update("${t.day}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value)); }); for (int i = 1; i <= 31; i++) { values.putIfAbsent("$i", () => 0.0); } } else if (this.type == 3) { records.forEach((element) { values.update("${element.createdAt}", (value) => value + element.value, ifAbsent: () => Converter.toDouble(element.value)); }); for (int i = 1; i <= 12; i++) { values.putIfAbsent("$i", () => 0.0); } } values.values.forEach((element) { _max = max(maxValue, Converter.toDouble(element)); }); double diff = _max % _valueSplit; _max = max(maxValue, _max + diff); print("$values --- $_max $maxValue $diff"); } @override void paint(Canvas canvas, Size size) { double zero = _zero = size.height - _labelHeight; // draw 值 行数 int valuePadding = 10; canvas.drawLine(Offset(_paddingLeft, zero), Offset(size.width, zero), _paint); ParagraphBuilder pb = ParagraphBuilder(_valueStyle) ..pushStyle(ui.TextStyle(color: Color(0xff999999))) ..addText("0"); ParagraphConstraints constraints = ParagraphConstraints(width: _paddingLeft - valuePadding); Paragraph paragraph = pb.build()..layout(constraints); paragraph.computeLineMetrics().forEach((element) { canvas.drawParagraph(paragraph, Offset(0, zero - element.baseline / 2)); }); canvas.drawLine(Offset(_paddingLeft, zero), Offset(_paddingLeft, 0), _paint); canvas.drawLine(Offset(size.width, zero), Offset(size.width, 0), _paint); double valueSpace = (size.height - _labelHeight) / _valueSize; for (var i = 0; i < _valueSize; i++) { canvas.drawLine(Offset(_paddingLeft, valueSpace * i), Offset(size.width, valueSpace * i), _paint); var value = (_valueSize - i) * _max ~/ _valueSize; ParagraphBuilder pb = ParagraphBuilder(_valueStyle) ..pushStyle(ui.TextStyle(color: Color(0xff999999))) ..addText(value < 1000 ? "${value}" : "${(value / 1000).toStringAsFixed(1)}k"); ParagraphConstraints constraints = ParagraphConstraints(width: _paddingLeft - valuePadding); Paragraph paragraph = pb.build()..layout(constraints); paragraph.computeLineMetrics().forEach((element) { canvas.drawParagraph(paragraph, Offset(0, valueSpace * i - element.baseline / 2)); }); // print("${value / 1000} ${_max} ${(_valueSize - i) * _max ~/ _valueSize}"); } // draw 数据列 double left = _paddingLeft + 10; double width = size.width - _paddingRight - left; // 柱子的有效空间 double valueStroke = 20; // 柱子宽度 List valueLabel = []; // 柱子数量 int scaleCount = 0; // 刻度数量 int scaleWeight = 3; if (this.type == 0) { // 日,按时间 scaleWeight = 2; valueLabel = List.generate(10, (index) => "${6 + index * scaleWeight}:00"); valueStroke = width / valueLabel.length / scaleWeight - 5; scaleCount = 18; } else if (this.type == 1) { left += 20; width -= 40; scaleWeight = 1; valueLabel = List.generate(7, (index) => WEEK[index]); valueStroke = 12; scaleCount = 6; } else if (this.type == 2) { DateTime now = this.dateTime; // DateTime now = DateTime(2019,2); DateTime startTime = DateTime.parse('${now.year}-${now.month < 10 ? '0${now.month}' : now.month}-01'); DateTime endTime = DateTime.parse('${now.year}-${now.month + 1 < 10 ? '0${now.month + 1}' : now.month + 1}-01'); int diffDay = endTime.difference(startTime).inDays; // List days = [1, 8, 15, 22, diffDay == 28 ? 28:29]; scaleCount = diffDay - 1; scaleWeight = 2; // valueLabel = days.map((e) => '$e/${now.month}').toList(); valueLabel = List.generate(diffDay ~/ scaleWeight + (scaleCount % scaleWeight == 0 ? 1 : 0), (index) => "${index * 2 + 1}"); valueStroke = 5; } else if (this.type == 3) { valueLabel = List.generate(12, (index) => '${index + 1}'); valueStroke = 12; scaleCount = 12 - 1; scaleWeight = 1; } int labelSize = valueLabel.length; double labelSpace = (width) / labelSize; double scaleWidth = (width) / scaleCount; // if (this.type < 2) { //draw 刻度 for (var i = 0; i <= scaleCount; i++) { canvas.drawLine(Offset(left + scaleWidth * i, zero), Offset(left + scaleWidth * i, zero - 3), _paint); } // } for (var i = 0; i < labelSize; i++) { String label = valueLabel[i]; ParagraphBuilder pb = ParagraphBuilder(_labelStyle) ..pushStyle(ui.TextStyle(color: Color(0xff999999))) ..addText(label); ParagraphConstraints constraints = ParagraphConstraints(width: labelSpace); Paragraph paragraph = pb.build()..layout(constraints); paragraph.computeLineMetrics().forEach((element) { canvas.drawParagraph(paragraph, Offset(left + scaleWidth * scaleWeight * i - element.width / 2, zero + 5)); }); // // double l = left + scaleWidth * scaleWeight * i - valueStroke / 2; // Rect rect = Rect.fromLTRB(l, zero, l + valueStroke, 0); // print("value draw $rect"); // Paint valuePaint = Paint() // ..shader = LinearGradient( // begin: Alignment.bottomCenter, // end: Alignment.topCenter, // colors: [Color(0xffFF9100), Color(0xffFFE600)], // ).createShader(rect); // canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(100)), valuePaint); } double maxVal = values.values.reduce((value, element) => max(value, element).toDouble()); // if (this.type < 2) { for (var i = 0; i <= scaleCount; i++) { String key = '${i + 1}'; // print("$key ${values[key]} 11111111111111 $scaleCount"); if (!values.containsKey(key)) continue; num value = values[key]; double l = left + scaleWidth * i - valueStroke / 2; Rect rect = Rect.fromLTRB(l, zero, l + valueStroke, calValue(value)); Paint valuePaint = Paint() ..shader = LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Color(0xffFF9100), Color(0xffFFE600)], ).createShader(rect); canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(100)), valuePaint); if (this.drawMax) { if (maxVal <= value) { if (maxVal > 0) { double offsetY = calValue(maxVal); double offsetX = _paddingLeft; while (offsetX < size.width) { canvas.drawLine(Offset(offsetX, offsetY), Offset(min(offsetX + 3, size.width), offsetY), _maxPaint); offsetX += 5; } ParagraphBuilder unit = ParagraphBuilder(_labelStyle) ..pushStyle(ui.TextStyle(color: _maxColor)) ..addText("${value.round()}${this.unit}"); ParagraphConstraints pc = ParagraphConstraints(width: size.width); paragraph = unit.build()..layout(pc); paragraph.computeLineMetrics().forEach((element) { var x = l + valueStroke / 2 - element.width / 2; canvas.drawParagraph(paragraph, Offset(x, offsetY -element.baseline - 10)); x = l + valueStroke / 2 ; canvas.drawPath(Path()..moveTo(x, offsetY - 4)..lineTo(x - 3, offsetY - 7)..lineTo(x + 3, offsetY - 7)..close(), _maxPaint); }); } } } } // } else { // Path path = Path(); // path.moveTo(_paddingLeft, calValue(values['1'])); // Path area = Path() // ..moveTo(_paddingLeft, zero) // ..lineTo(_paddingLeft, calValue(values['1'])); // for (var i = 1; i <= scaleCount; i++) { // path.lineTo(left + scaleWidth * i, calValue(values['$i'])); // area.lineTo(left + scaleWidth * i, calValue(values['$i'])); // } // area.lineTo(size.width, zero); // area.close(); // // canvas.save(); // canvas.clipPath(area); // Rect rect = Rect.fromLTRB(_paddingLeft, zero, size.width, calValue(_max)); // Paint valuePaint = Paint() // ..shader = LinearGradient( // begin: Alignment.bottomCenter, // end: Alignment.topCenter, // colors: [Color(0xffFFC400), Color(0x50FFFFFF)], // ).createShader(rect); // canvas.drawRect(rect, valuePaint); // canvas.restore(); // // canvas.drawPath(path, _linePaint); // } } double calValue(num value) { if (value == null) return _zero; return max(0, _zero - _zero * value / _max); } @override bool shouldRepaint(CustomPainter oldDelegate) { return false; } }