Divided the code into functional modules
This commit is contained in:
226
UI/HeartRateGraphDrawable.cs
Normal file
226
UI/HeartRateGraphDrawable.cs
Normal file
@ -0,0 +1,226 @@
|
||||
using HeartRateMonitorAndroid.Models;
|
||||
using Microsoft.Maui.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace HeartRateMonitorAndroid.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 心率图表绘制类
|
||||
/// </summary>
|
||||
public class HeartRateGraphDrawable : IDrawable
|
||||
{
|
||||
private List<HeartRateDataPoint> _dataPoints = [];
|
||||
private int _maxPoints = 100; // 最多显示100个数据点
|
||||
private int _minHeartRate = 40;
|
||||
private int _maxHeartRate = 180;
|
||||
|
||||
// 图表配色方案
|
||||
private readonly Color _backgroundColor = Color.FromArgb("#F8F9FA"); // 浅灰背景色
|
||||
private readonly Color _gridLineColor = Color.FromArgb("#E9ECEF"); // 网格线颜色
|
||||
private readonly Color _axisColor = Color.FromArgb("#CED4DA"); // 坐标轴颜色
|
||||
private readonly Color _textColor = Color.FromArgb("#6C757D"); // 文本颜色
|
||||
private readonly Color _heartRateLineColor = Color.FromArgb("#FF4757"); // 心率线颜色
|
||||
private readonly Color _heartRateAreaColor = Color.FromRgba(255, 71, 87, 0.2); // 心率区域填充颜色
|
||||
private readonly Color _heartRatePointColor = Color.FromArgb("#FF4757"); // 数据点颜色
|
||||
private readonly Color _accentColor = Color.FromArgb("#2E86DE"); // 强调色
|
||||
|
||||
public void UpdateData(List<HeartRateDataPoint> dataPoints)
|
||||
{
|
||||
_dataPoints = dataPoints.ToList();
|
||||
// 如果有数据,动态调整Y轴范围
|
||||
if (_dataPoints.Count > 0)
|
||||
{
|
||||
_minHeartRate = Math.Max(40, _dataPoints.Min(p => p.HeartRate) - 10);
|
||||
_maxHeartRate = Math.Min(200, _dataPoints.Max(p => p.HeartRate) + 10);
|
||||
|
||||
// 确保Y轴范围合理
|
||||
int range = _maxHeartRate - _minHeartRate;
|
||||
if (range < 30) // 如果范围太小,扩大它
|
||||
{
|
||||
_minHeartRate = Math.Max(40, _minHeartRate - (30 - range) / 2);
|
||||
_maxHeartRate = Math.Min(200, _maxHeartRate + (30 - range) / 2);
|
||||
}
|
||||
|
||||
// 圆整到最接近的10
|
||||
_minHeartRate = (_minHeartRate / 10) * 10;
|
||||
_maxHeartRate = ((_maxHeartRate + 9) / 10) * 10;
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(ICanvas canvas, RectF dirtyRect)
|
||||
{
|
||||
// 设置背景色
|
||||
canvas.FillColor = _backgroundColor;
|
||||
canvas.FillRectangle(dirtyRect);
|
||||
|
||||
if (_dataPoints.Count < 2) return; // 至少需要两个点才能绘制线条
|
||||
|
||||
// 计算绘图区域,增加左侧留白以放置y轴标签
|
||||
float leftPadding = 45;
|
||||
float rightPadding = 20;
|
||||
float topPadding = 30;
|
||||
float bottomPadding = 40;
|
||||
|
||||
float graphWidth = dirtyRect.Width - leftPadding - rightPadding;
|
||||
float graphHeight = dirtyRect.Height - topPadding - bottomPadding;
|
||||
float graphBottom = dirtyRect.Height - bottomPadding;
|
||||
float graphTop = topPadding;
|
||||
float graphLeft = leftPadding;
|
||||
float graphRight = dirtyRect.Width - rightPadding;
|
||||
|
||||
// 绘制背景和边框
|
||||
canvas.FillColor = Colors.White;
|
||||
canvas.FillRoundedRectangle(graphLeft - 5, graphTop - 5, graphWidth + 10, graphHeight + 10, 8);
|
||||
canvas.StrokeColor = _gridLineColor;
|
||||
canvas.StrokeSize = 1;
|
||||
canvas.DrawRoundedRectangle(graphLeft - 5, graphTop - 5, graphWidth + 10, graphHeight + 10, 8);
|
||||
|
||||
// 绘制网格线
|
||||
canvas.StrokeColor = _gridLineColor;
|
||||
canvas.StrokeSize = 1;
|
||||
canvas.StrokeDashPattern = new float[] { 4, 4 }; // 虚线网格
|
||||
|
||||
// 水平网格线和心率刻度
|
||||
int yStep = (_maxHeartRate - _minHeartRate) > 100 ? 40 : 20; // 根据范围动态调整步长
|
||||
for (int hr = _minHeartRate; hr <= _maxHeartRate; hr += yStep)
|
||||
{
|
||||
float y = graphBottom - ((hr - _minHeartRate) * graphHeight / (_maxHeartRate - _minHeartRate));
|
||||
|
||||
// 绘制网格线
|
||||
canvas.DrawLine(graphLeft, y, graphRight, y);
|
||||
|
||||
// 绘制心率刻度
|
||||
canvas.FontSize = 12;
|
||||
canvas.FontColor = _textColor;
|
||||
canvas.DrawString(hr.ToString(), graphLeft - 25, y, HorizontalAlignment.Center);
|
||||
}
|
||||
|
||||
// 重置虚线模式
|
||||
canvas.StrokeDashPattern = null;
|
||||
|
||||
// 时间刻度线和标签
|
||||
if (_dataPoints.Count > 0)
|
||||
{
|
||||
int pointCount = _dataPoints.Count;
|
||||
int xStep = Math.Max(1, pointCount / 5); // 大约显示5个时间点
|
||||
|
||||
for (int i = 0; i < pointCount; i += xStep)
|
||||
{
|
||||
if (i >= pointCount) break;
|
||||
float x = graphLeft + (i * graphWidth / (pointCount - 1));
|
||||
|
||||
// 绘制垂直网格线
|
||||
canvas.StrokeColor = _gridLineColor;
|
||||
canvas.StrokeDashPattern = [4, 4];
|
||||
canvas.DrawLine(x, graphTop, x, graphBottom);
|
||||
canvas.StrokeDashPattern = null;
|
||||
|
||||
// 绘制时间刻度(分钟:秒)
|
||||
canvas.FontSize = 12;
|
||||
canvas.FontColor = _textColor;
|
||||
string timeLabel = _dataPoints[i].Timestamp.ToString("mm:ss");
|
||||
canvas.DrawString(timeLabel, x, graphBottom + 15, HorizontalAlignment.Center);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制坐标轴
|
||||
canvas.StrokeColor = _axisColor;
|
||||
canvas.StrokeSize = 2;
|
||||
canvas.DrawLine(graphLeft, graphBottom, graphRight, graphBottom); // X轴
|
||||
canvas.DrawLine(graphLeft, graphTop, graphLeft, graphBottom); // Y轴
|
||||
|
||||
// 添加标题
|
||||
canvas.FontColor = _accentColor;
|
||||
canvas.FontSize = 14;
|
||||
canvas.DrawString("心率监测图表", dirtyRect.Width / 2, graphTop - 15, HorizontalAlignment.Center);
|
||||
|
||||
// 创建心率曲线路径
|
||||
PathF linePath = new PathF();
|
||||
PathF areaPath = new PathF();
|
||||
bool isFirst = true;
|
||||
|
||||
// 添加区域填充起始点
|
||||
areaPath.MoveTo(graphLeft, graphBottom);
|
||||
|
||||
for (int i = 0; i < _dataPoints.Count; i++)
|
||||
{
|
||||
float x = graphLeft + (i * graphWidth / (_dataPoints.Count - 1));
|
||||
float y = graphBottom - ((_dataPoints[i].HeartRate - _minHeartRate) * graphHeight /
|
||||
(_maxHeartRate - _minHeartRate));
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
linePath.MoveTo(x, y);
|
||||
areaPath.LineTo(x, y);
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用曲线而不是直线,使图表更平滑
|
||||
if (i > 0 && i < _dataPoints.Count - 1)
|
||||
{
|
||||
float prevX = graphLeft + ((i - 1) * graphWidth / (_dataPoints.Count - 1));
|
||||
float prevY = graphBottom - ((_dataPoints[i - 1].HeartRate - _minHeartRate) * graphHeight /
|
||||
(_maxHeartRate - _minHeartRate));
|
||||
float nextX = graphLeft + ((i + 1) * graphWidth / (_dataPoints.Count - 1));
|
||||
float nextY = graphBottom - ((_dataPoints[i + 1].HeartRate - _minHeartRate) * graphHeight /
|
||||
(_maxHeartRate - _minHeartRate));
|
||||
|
||||
float cpx1 = prevX + (x - prevX) * 0.5f;
|
||||
float cpy1 = prevY;
|
||||
float cpx2 = x - (x - prevX) * 0.5f;
|
||||
float cpy2 = y;
|
||||
|
||||
linePath.CurveTo(cpx1, cpy1, cpx2, cpy2, x, y);
|
||||
areaPath.CurveTo(cpx1, cpy1, cpx2, cpy2, x, y);
|
||||
}
|
||||
else
|
||||
{
|
||||
linePath.LineTo(x, y);
|
||||
areaPath.LineTo(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 完成区域填充路径
|
||||
areaPath.LineTo(graphLeft + graphWidth, graphBottom);
|
||||
areaPath.LineTo(graphLeft, graphBottom);
|
||||
areaPath.Close();
|
||||
|
||||
// 绘制区域填充
|
||||
canvas.FillColor = _heartRateAreaColor;
|
||||
canvas.FillPath(areaPath);
|
||||
|
||||
// 绘制曲线
|
||||
canvas.StrokeColor = _heartRateLineColor;
|
||||
canvas.StrokeSize = 3;
|
||||
canvas.DrawPath(linePath);
|
||||
|
||||
// 只绘制最新数据点
|
||||
if (_dataPoints.Count > 0)
|
||||
{
|
||||
// 获取最新数据点的位置
|
||||
int lastIndex = _dataPoints.Count - 1;
|
||||
float x = graphLeft + (lastIndex * graphWidth / (_dataPoints.Count - 1));
|
||||
float y = graphBottom - ((_dataPoints[lastIndex].HeartRate - _minHeartRate) * graphHeight /
|
||||
(_maxHeartRate - _minHeartRate));
|
||||
|
||||
// 绘制最新点的标记
|
||||
canvas.FillColor = _heartRatePointColor;
|
||||
canvas.FillCircle(x, y, 6);
|
||||
canvas.StrokeSize = 2;
|
||||
canvas.StrokeColor = Colors.White;
|
||||
canvas.DrawCircle(x, y, 6);
|
||||
|
||||
// 显示最新心率值
|
||||
canvas.FontSize = 12;
|
||||
canvas.FontColor = _heartRateLineColor;
|
||||
//canvas.Font = FontAttributes.Bold;
|
||||
canvas.DrawString(_dataPoints[lastIndex].HeartRate + " bpm",
|
||||
x, y - 15, HorizontalAlignment.Center);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user