import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:intl/intl.dart'; class SnoreChartContainer extends StatelessWidget { final List snoreValues; final List barData; final List showLabel; final int startTime; final int endTime; const SnoreChartContainer({ required this.snoreValues, required this.barData, required this.showLabel, required this.startTime, required this.endTime, super.key, }); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SnoreBarOverlay( barData: barData, showLabel: showLabel, startTime: startTime, endTime: endTime, ), Container(height: 32.rpx), Container( height: 23.rpx, child: SnoreWaveform( snoreValues: snoreValues, startTime: startTime, endTime: endTime, ), ), ], ); } } class SnoreBarOverlay extends StatelessWidget { final List barData; final List showLabel; // ✅ 加入 final int startTime; final int endTime; const SnoreBarOverlay({ required this.barData, required this.showLabel, // ✅ 加入 required this.startTime, required this.endTime, super.key, }); @override Widget build(BuildContext context) { const double barHeight = 50; return SizedBox( height: barHeight, child: CustomPaint( size: Size(double.infinity, barHeight), painter: SnoreBarPainter( barData: barData, showLabel: showLabel, // ✅ 加入 startTime: startTime, endTime: endTime, ), ), ); } } class SnoreBarPainter extends CustomPainter { final List barData; final List showLabel; // ✅ 加入 final int startTime; final int endTime; SnoreBarPainter({ required this.barData, required this.showLabel, // ✅ 加入 required this.startTime, required this.endTime, }); @override void paint(Canvas canvas, Size size) { final double width = size.width; final double height = size.height; final double pixelPerMs = width / (endTime - startTime); for (var item in barData) { final int st = item['st']; final int et = item['et']; final int type = item['type']; // 查找匹配的颜色 final match = showLabel.firstWhere( (e) => e['type'] == type, orElse: () => null, ); Color barColor = Colors.transparent; if (match != null) { final dynamic colorStr = match['color']; if (colorStr != null && colorStr.toString().isNotEmpty) { barColor = stringToColor(colorStr); } } final Paint barPaint = Paint() ..color = barColor ..style = PaintingStyle.fill; final double leftX = (st - startTime) * pixelPerMs; final double rightX = (et - startTime) * pixelPerMs; final double barWidth = rightX - leftX; final double barHeight = (type + 5).toDouble() * 8; final double top = height - barHeight; final rect = Rect.fromLTWH(leftX, top, barWidth, barHeight); canvas.drawRect(rect, barPaint); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } class SnoreWaveform extends StatelessWidget { final List snoreValues; final int startTime; final int endTime; const SnoreWaveform({ required this.snoreValues, required this.startTime, required this.endTime, super.key, }); @override Widget build(BuildContext context) { return SizedBox( height: 150, child: CustomPaint( size: Size(double.infinity, 150), painter: SnoreWaveformPainter( snoreValues: snoreValues, startTime: startTime, endTime: endTime, ), ), ); } } // class SnoreWaveformPainter extends CustomPainter { // final List snoreValues; // final int startTime; // final int endTime; // SnoreWaveformPainter({ // required this.snoreValues, // required this.startTime, // required this.endTime, // }); // @override // void paint(Canvas canvas, Size size) { // final double width = size.width; // final double height = size.height; // final double centerY = height / 2; // final double totalDuration = (endTime - startTime).toDouble(); // final double pixelPerMs = width / totalDuration; // final Paint wavePaint = Paint() // ..color = stringToColor("#8E7DEF") // ..strokeWidth = 1.5 // ..style = PaintingStyle.stroke; // final Path upperPath = Path(); // final Path lowerPath = Path(); // const double scaleY = 0.5; //波形图比例 // for (int i = 0; i < snoreValues.length; i++) { // final timestamp = snoreValues[i]["st"]; // final value = snoreValues[i]["value"]?.toDouble() ?? 0; // final x = (timestamp - startTime) * pixelPerMs; // final y = centerY - value * scaleY; // final yMirror = centerY + value * scaleY; // if (i == 0) { // upperPath.moveTo(x, y); // lowerPath.moveTo(x, yMirror); // } else { // upperPath.lineTo(x, y); // lowerPath.lineTo(x, yMirror); // } // } // canvas.drawPath(upperPath, wavePaint); // canvas.drawPath(lowerPath, wavePaint); // final Paint axisPaint = Paint() // ..color = Colors.grey // ..strokeWidth = 0.5; // // 画中心线 // canvas.drawLine(Offset(0, centerY), Offset(width, centerY), axisPaint); // // 时间刻度绘制 // final textPainter = TextPainter( // textAlign: TextAlign.center, // textDirection: ui.TextDirection.ltr, // ); // final int hourMs = 60 * 60 * 1000; // // 循环绘制整点小时标签(不包含终点) // for (int t = startTime; t < endTime; t += hourMs) { // double x = (t - startTime) * pixelPerMs; // DateTime dt = DateTime.fromMillisecondsSinceEpoch(t); // String label; // if (t == startTime) { // label = DateFormat('HH:mm').format(dt); // 起点显示 HH:mm // } else { // label = DateFormat('h').format(dt); // 中间显示小时,不带前导0 // } // textPainter.text = TextSpan( // text: label, // style: TextStyle(fontSize: 10, color: Colors.grey), // ); // textPainter.layout(); // textPainter.paint( // canvas, // Offset(x - textPainter.width / 2, height + 20.rpx), // ); // } // // 单独绘制终点时间标签,确保显示具体时分 // { // double x = (endTime - startTime) * pixelPerMs; // DateTime dt = DateTime.fromMillisecondsSinceEpoch(endTime); // String label = DateFormat('HH:mm').format(dt); // textPainter.text = TextSpan( // text: label, // style: TextStyle(fontSize: 10, color: Colors.grey), // ); // textPainter.layout(); // textPainter.paint( // canvas, // Offset(x - textPainter.width / 2, height + 20.rpx), // ); // } // } // @override // bool shouldRepaint(covariant CustomPainter oldDelegate) => true; // } class SnoreWaveformPainter extends CustomPainter { final List snoreValues; final int startTime; final int endTime; SnoreWaveformPainter({ required this.snoreValues, required this.startTime, required this.endTime, }); @override void paint(Canvas canvas, Size size) { final double width = size.width; final double height = size.height; final double centerY = height / 2; final double totalDuration = (endTime - startTime).toDouble(); final double pixelPerMs = width / totalDuration; final Paint wavePaint = Paint() ..color = stringToColor("#8E7DEF").withOpacity(0.8) ..strokeWidth = 1.5 ..style = PaintingStyle.stroke; final Path upperPath = Path(); final Path lowerPath = Path(); // ✅ 获取最大值用于自适应比例 double maxValue = snoreValues.fold(0, (prev, e) { final value = e["value"]?.toDouble() ?? 0; return value > prev ? value : prev; }); // ✅ 自适应缩放比例,限制波形最大高度为 height * 0.45 final double maxWaveHeight = height * 1; final double scaleY = maxValue > 0 ? (maxWaveHeight / maxValue) : 1; for (int i = 0; i < snoreValues.length; i++) { final timestamp = snoreValues[i]["st"]; final value = snoreValues[i]["value"]?.toDouble() ?? 0; final x = (timestamp - startTime) * pixelPerMs; final y = centerY - value * scaleY; final yMirror = centerY + value * scaleY; if (i == 0) { upperPath.moveTo(x, y); lowerPath.moveTo(x, yMirror); } else { upperPath.lineTo(x, y); lowerPath.lineTo(x, yMirror); } } canvas.drawPath(upperPath, wavePaint); canvas.drawPath(lowerPath, wavePaint); // ✅ 最后绘制中心线,防止被覆盖 final Paint axisPaint = Paint() ..color = Colors.grey.withOpacity(0.6) ..strokeWidth = 0.5; canvas.drawLine(Offset(0, centerY), Offset(width, centerY), axisPaint); // ✅ 时间刻度绘制 final textPainter = TextPainter( textAlign: TextAlign.center, textDirection: ui.TextDirection.ltr, ); final int hourMs = 60 * 60 * 1000; for (int t = startTime; t < endTime; t += hourMs) { double x = (t - startTime) * pixelPerMs; DateTime dt = DateTime.fromMillisecondsSinceEpoch(t); String label = t == startTime ? DateFormat('HH:mm').format(dt) : DateFormat('h').format(dt); // 12小时制 textPainter.text = TextSpan( text: label, style: TextStyle(fontSize: 10, color: Colors.grey), ); textPainter.layout(); textPainter.paint( canvas, Offset(x - textPainter.width / 2, height + 2), // 标签显示在底部 ); } // ✅ 画终点时间 { double x = (endTime - startTime) * pixelPerMs; DateTime dt = DateTime.fromMillisecondsSinceEpoch(endTime); String label = DateFormat('HH:mm').format(dt); textPainter.text = TextSpan( text: label, style: TextStyle(fontSize: 10, color: Colors.grey), ); textPainter.layout(); textPainter.paint( canvas, Offset(x - textPainter.width / 2, height + 2), ); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }