更新睡眠报告
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
class DotBarChart extends StatefulWidget {
|
||||
final List<Map<String, dynamic>> showLabel;
|
||||
@@ -39,7 +40,6 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
void _showTooltip(BuildContext context, Map<String, dynamic> data,
|
||||
Offset position, double dotY) {
|
||||
_removeOverlay();
|
||||
|
||||
final RenderBox renderBox = context.findRenderObject() as RenderBox;
|
||||
final Offset globalPosition = renderBox.localToGlobal(position);
|
||||
|
||||
@@ -55,11 +55,6 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
color: themeController.currentColor.sc5,
|
||||
borderRadius: BorderRadius.circular(8.rpx),
|
||||
boxShadow: [
|
||||
// BoxShadow(
|
||||
// color: Colors.black.withOpacity(0.6),
|
||||
// blurRadius: 10.rpx,
|
||||
// offset: Offset(0, 4.rpx),
|
||||
// ),
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
blurRadius: 12.rpx,
|
||||
@@ -92,7 +87,6 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Overlay.of(context)?.insert(_overlayEntry!);
|
||||
}
|
||||
|
||||
@@ -103,7 +97,6 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
int maxTimes = widget.showLabel
|
||||
.map((e) => e['times'] ?? 0)
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
|
||||
int yMax = (maxTimes / 10).ceil() * 10;
|
||||
if (yMax == 0) yMax = 10;
|
||||
|
||||
@@ -113,23 +106,35 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
int displayMax = step * maxSteps;
|
||||
List<int> yLabels = List.generate(maxSteps + 1, (index) => step * index);
|
||||
|
||||
DateFormat fullFormat = DateFormat('HH:mm');
|
||||
DateFormat hourFormat = DateFormat('H');
|
||||
DateTime startDate = DateTime.fromMillisecondsSinceEpoch(widget.startTime);
|
||||
DateTime endDate = DateTime.fromMillisecondsSinceEpoch(widget.endTime);
|
||||
|
||||
int maxXLabels = 11;
|
||||
int totalPoints = widget.showLabel.length;
|
||||
List<int> xLabelIndices = [];
|
||||
// Generate hourly timestamps
|
||||
List<int> hourlyTimestamps = [];
|
||||
DateTime currentHour = DateTime(
|
||||
startDate.year, startDate.month, startDate.day, startDate.hour);
|
||||
while (currentHour.isBefore(endDate) ||
|
||||
currentHour.isAtSameMomentAs(endDate)) {
|
||||
hourlyTimestamps.add(currentHour.millisecondsSinceEpoch);
|
||||
currentHour = currentHour.add(const Duration(hours: 1));
|
||||
}
|
||||
|
||||
if (totalPoints <= maxXLabels) {
|
||||
xLabelIndices = List.generate(totalPoints, (i) => i);
|
||||
} else {
|
||||
xLabelIndices.add(0);
|
||||
int middleCount = maxXLabels - 2;
|
||||
double stepX = (totalPoints - 1) / (middleCount + 1);
|
||||
for (int i = 1; i <= middleCount; i++) {
|
||||
xLabelIndices.add((stepX * i).round());
|
||||
// Calculate positions for hourly labels
|
||||
List<Map<String, dynamic>> hourLabels = [];
|
||||
if (widget.showLabel.isNotEmpty) {
|
||||
int firstDataTime = widget.showLabel.first['time'];
|
||||
int lastDataTime = widget.showLabel.last['time'];
|
||||
double totalDuration = (lastDataTime - firstDataTime).toDouble();
|
||||
|
||||
for (int timestamp in hourlyTimestamps) {
|
||||
if (timestamp >= firstDataTime && timestamp <= lastDataTime) {
|
||||
double position = (timestamp - firstDataTime) / totalDuration;
|
||||
hourLabels.add({
|
||||
'time': timestamp,
|
||||
'position': position,
|
||||
});
|
||||
}
|
||||
}
|
||||
xLabelIndices.add(totalPoints - 1);
|
||||
}
|
||||
|
||||
double yAxisWidth = 36.rpx;
|
||||
@@ -225,19 +230,14 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
drawableHeight * (1 - times / displayMax);
|
||||
|
||||
return Positioned(
|
||||
left: x - 20.rpx, // Increase touch area
|
||||
top: y - 20.rpx, // Increase touch area
|
||||
left: x - 20.rpx,
|
||||
top: y - 20.rpx,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedIndex = index;
|
||||
});
|
||||
_showTooltip(
|
||||
context,
|
||||
data,
|
||||
Offset(x, y),
|
||||
y,
|
||||
);
|
||||
_showTooltip(context, data, Offset(x, y), y);
|
||||
},
|
||||
child: Container(
|
||||
width: 40.rpx,
|
||||
@@ -259,33 +259,15 @@ class _DotBarChartState extends State<DotBarChart> {
|
||||
padding: EdgeInsets.only(left: yAxisWidth),
|
||||
child: SizedBox(
|
||||
height: xAxisHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: List.generate(widget.showLabel.length, (index) {
|
||||
String label = '';
|
||||
if (xLabelIndices.contains(index)) {
|
||||
DateTime dt = DateTime.fromMillisecondsSinceEpoch(
|
||||
widget.showLabel[index]['time']);
|
||||
if (index == 0 || index == totalPoints - 1) {
|
||||
label = fullFormat.format(dt);
|
||||
} else {
|
||||
label = dt.hour.toString();
|
||||
}
|
||||
}
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 14.rpx),
|
||||
child: Text(
|
||||
label,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 18.rpx,
|
||||
color: themeController.currentColor.sc4,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
child: CustomPaint(
|
||||
size: Size(double.infinity, xAxisHeight),
|
||||
painter: _XAxisPainter(
|
||||
hourLabels: hourLabels,
|
||||
textColor: themeController.currentColor.sc4,
|
||||
fontSize: 18.rpx,
|
||||
startTime: widget.startTime,
|
||||
endTime: widget.endTime,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -328,7 +310,6 @@ class _DotBarChartPainter extends CustomPainter {
|
||||
|
||||
double thresholdY =
|
||||
yAxisTopPadding + drawableHeight * (1 - threshold / yMax);
|
||||
|
||||
drawDashedLine(
|
||||
canvas,
|
||||
Offset(0, thresholdY),
|
||||
@@ -339,7 +320,6 @@ class _DotBarChartPainter extends CustomPainter {
|
||||
);
|
||||
|
||||
final double dotRadius = 13.rpx;
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
int times = data[i]['times'] ?? 0;
|
||||
double x = horizontalPadding + i * xStep;
|
||||
@@ -357,10 +337,8 @@ class _DotBarChartPainter extends CustomPainter {
|
||||
..style = PaintingStyle.stroke
|
||||
..color = Colors.white
|
||||
..strokeWidth = 3.rpx;
|
||||
|
||||
canvas.drawCircle(Offset(x, y), dotRadius + 1.rpx, borderPaint);
|
||||
}
|
||||
|
||||
canvas.drawCircle(Offset(x, y), dotRadius, dotPaint);
|
||||
}
|
||||
|
||||
@@ -376,7 +354,6 @@ class _DotBarChartPainter extends CustomPainter {
|
||||
|
||||
for (int i = 0; i < yLabelsCount; i++) {
|
||||
double y = yAxisTopPadding + i * (drawableHeight / (yLabelsCount - 1));
|
||||
|
||||
if (i == yLabelsCount - 1) {
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), solidLinePaint);
|
||||
} else {
|
||||
@@ -392,8 +369,14 @@ class _DotBarChartPainter extends CustomPainter {
|
||||
}
|
||||
}
|
||||
|
||||
void drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint,
|
||||
double dashWidth, double gapWidth) {
|
||||
void drawDashedLine(
|
||||
Canvas canvas,
|
||||
Offset start,
|
||||
Offset end,
|
||||
Paint paint,
|
||||
double dashWidth,
|
||||
double gapWidth,
|
||||
) {
|
||||
double dx = start.dx;
|
||||
final double y = start.dy;
|
||||
while (dx < end.dx) {
|
||||
@@ -406,3 +389,65 @@ class _DotBarChartPainter extends CustomPainter {
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
||||
|
||||
class _XAxisPainter extends CustomPainter {
|
||||
final List<Map<String, dynamic>> hourLabels;
|
||||
final Color textColor;
|
||||
final double fontSize;
|
||||
final int startTime;
|
||||
final int endTime;
|
||||
|
||||
_XAxisPainter({
|
||||
required this.hourLabels,
|
||||
required this.textColor,
|
||||
required this.fontSize,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final textStyle = TextStyle(
|
||||
color: textColor,
|
||||
fontSize: fontSize,
|
||||
);
|
||||
final textPainter = TextPainter(
|
||||
textDirection: ui.TextDirection.ltr,
|
||||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
// Draw start time (leftmost)
|
||||
final startText = DateFormat('HH:mm')
|
||||
.format(DateTime.fromMillisecondsSinceEpoch(startTime));
|
||||
final startTextSpan = TextSpan(text: startText, style: textStyle);
|
||||
textPainter.text = startTextSpan;
|
||||
textPainter.layout();
|
||||
textPainter.paint(canvas, Offset(0, 14.rpx));
|
||||
|
||||
// Draw end time (rightmost)
|
||||
final endText = DateFormat('HH:mm')
|
||||
.format(DateTime.fromMillisecondsSinceEpoch(endTime));
|
||||
final endTextSpan = TextSpan(text: endText, style: textStyle);
|
||||
textPainter.text = endTextSpan;
|
||||
textPainter.layout();
|
||||
textPainter.paint(canvas, Offset(size.width - textPainter.width, 14.rpx));
|
||||
|
||||
// Draw hourly labels in between
|
||||
for (var label in hourLabels) {
|
||||
final position = label['position'] * size.width;
|
||||
final time = DateTime.fromMillisecondsSinceEpoch(label['time']);
|
||||
final hourText = DateFormat('h').format(time);
|
||||
final textSpan = TextSpan(text: hourText, style: textStyle);
|
||||
textPainter.text = textSpan;
|
||||
textPainter.layout();
|
||||
final offset = Offset(
|
||||
position - textPainter.width / 2,
|
||||
14.rpx, // Padding from bottom
|
||||
);
|
||||
textPainter.paint(canvas, offset);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user