792 lines
29 KiB
Dart
792 lines
29 KiB
Dart
import 'package:ef/base/chart/drawer.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||
import 'package:vbvs_app/pages/mh_page/component/easychart.dart';
|
||
import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart';
|
||
import 'package:vbvs_app/pages/sleep_report/component/SleepChartWidget.dart';
|
||
import 'package:vbvs_app/pages/sleep_report/component/TrendDataTablePage.dart';
|
||
import 'package:vbvs_app/pages/sleep_report/component/TrendDataTextPage.dart';
|
||
import 'package:vbvs_app/pages/sleep_report/component/WeekSleepScoreWidget.dart';
|
||
|
||
Widget MonthDataWidget(
|
||
Map<dynamic, dynamic> sleepReport,
|
||
dynamic data,
|
||
) {
|
||
List<Widget> _buildSectionList() {
|
||
EdgeInsetsDirectional padding =
|
||
EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 25.rpx);
|
||
|
||
return [
|
||
AvgSleepScoreWidget(
|
||
sleepReport: sleepReport,
|
||
mediumLabel: "本月平均分",
|
||
), //睡眠评分
|
||
SleepChartContainer(
|
||
title: "每日得分",
|
||
tipText: "用户本月睡眠分数的汇总。",
|
||
sleepReport: sleepReport,
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: sleepReport['scoreList']['yRange']['min'] ?? 0.0,
|
||
max: sleepReport['scoreList']['yRange']['max'] ?? 100.0,
|
||
q: 100,
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['yLable']
|
||
.map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-15.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildValueTexts(sleepReport['scoreList']['data'], '分', 1),
|
||
xCount: buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['scoreList']['yLable'].length,
|
||
points: buildMonthlyChartData(sleepReport['scoreList'])['points'],
|
||
displayMode: ChartDisplayMode.bar,
|
||
barColors: buildMonthlyChartData(sleepReport['scoreList'])['colors'],
|
||
xUnit: sleepReport['scoreList']['yUnit'],
|
||
barWidth: 0.5,
|
||
),
|
||
showLabel: sleepReport['scoreList']['type'],
|
||
),
|
||
SleepCard(
|
||
sleepReport: sleepReport['cwl'],
|
||
highlightItem: data['itemName'],
|
||
),
|
||
IndicatorCompareCard(
|
||
title: "与上月对比",
|
||
headers: ["名称", "上月", "本月", "参考范围"],
|
||
tooltip: "睡眠分数与上月分数进行对比,是通过量化分析近期睡眠质量变化,可了解自身睡眠状态的波动情况,进而调整用户的作息习惯。",
|
||
rows: (sleepReport['cwl'] ?? []).map<List<Widget>>((item) {
|
||
return [
|
||
Text(
|
||
item['name']?.toString() ?? '-',
|
||
style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx),
|
||
),
|
||
Text(
|
||
item['lastCurr']?.toString() ?? '-',
|
||
style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx),
|
||
),
|
||
Text(
|
||
item['curr']?.toString() ?? '-',
|
||
style: TextStyle(
|
||
color: item["currColor"] ?? Color(0xFFFFFFFF),
|
||
fontSize: 26.rpx,
|
||
),
|
||
),
|
||
Text(
|
||
item['range']?.toString() ?? '-',
|
||
style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx),
|
||
),
|
||
];
|
||
}).toList(),
|
||
),
|
||
SleepChartContainer(
|
||
title: "本月睡眠时长",
|
||
tipText: "本月睡眠时长是指从月初到月末,用户每天实际睡眠的时间总和。",
|
||
sleepReport: sleepReport,
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: sleepReport['scoreList']['yRange']['min'] ?? 0.0,
|
||
max: sleepReport['scoreList']['yRange']['max'] ?? 10.0,
|
||
q: 10,
|
||
labels: List<String>.from(
|
||
sleepReport['csd']['yLable'].map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-15.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildSleepValueTexts(sleepReport['csd']['data'], '小时', 1),
|
||
xCount: buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['csd']['yLable'].length,
|
||
points: [],
|
||
dualBarPoints: buildTripleBarData(sleepReport['csd']),
|
||
displayMode: ChartDisplayMode.dualBar,
|
||
xUnit: sleepReport['csd']['yUnit'],
|
||
barWidth: 0.5,
|
||
),
|
||
showLabel: sleepReport['csd']['type'],
|
||
),
|
||
|
||
TrendDataTablePage(
|
||
title: sleepReport['dysp'][0]['name'],
|
||
tipText: sleepReport['dysp'][0]['tips'],
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style:
|
||
TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: 0,
|
||
max: 5,
|
||
q: 5,
|
||
labels: List<String>.from(
|
||
sleepReport['dysp'][0]['yLable']
|
||
.map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-30.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildValueTexts(sleepReport['dysp'][0]['value'], '入睡时间:', 0),
|
||
xCount:
|
||
buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['dysp'][0]['yLable'].length,
|
||
points: buildGeneralPoints(sleepReport['dysp'][0]),
|
||
displayMode: ChartDisplayMode.line,
|
||
xUnit: sleepReport['dysp'][0]['yUnit'],
|
||
barWidth: 0.5,
|
||
bottomPadding: 16,
|
||
),
|
||
padding: 45.rpx),
|
||
TrendDataTablePage(
|
||
title: sleepReport['dysp'][1]['name'],
|
||
tipText: sleepReport['dysp'][1]['tips'],
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style:
|
||
TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: 0,
|
||
max: 4,
|
||
q: 4,
|
||
labels: List<String>.from(
|
||
sleepReport['dysp'][1]['yLable']
|
||
.map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-30.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildValueTexts(sleepReport['dysp'][1]['value'], '起床时间:', 0),
|
||
xCount:
|
||
buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['dysp'][1]['yLable'].length,
|
||
points: buildGeneralPoints(sleepReport['dysp'][1]),
|
||
displayMode: ChartDisplayMode.line,
|
||
xUnit: sleepReport['dysp'][1]['yUnit'],
|
||
barWidth: 0.5,
|
||
bottomPadding: 16,
|
||
),
|
||
padding: 45.rpx),
|
||
TrendDataTablePage(
|
||
title: sleepReport['dysp'][2]['name'],
|
||
tipText: sleepReport['dysp'][2]['tips'],
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style:
|
||
TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: 0,
|
||
max: 4,
|
||
q: 4,
|
||
labels: List<String>.from(
|
||
sleepReport['dysp'][2]['yLable']
|
||
.map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-15.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildValueTexts(sleepReport['dysp'][2]['value'], '次', 1),
|
||
xCount:
|
||
buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['dysp'][2]['yLable'].length,
|
||
points: buildGeneralPoints(sleepReport['dysp'][2]),
|
||
displayMode: ChartDisplayMode.line,
|
||
xUnit: sleepReport['dysp'][2]['yUnit'],
|
||
barWidth: 0.5,
|
||
bottomPadding: 16,
|
||
),
|
||
padding: 45.rpx),
|
||
TrendDataTablePage(
|
||
title: sleepReport['dysp'][3]['name'],
|
||
tipText: sleepReport['dysp'][3]['tips'],
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style:
|
||
TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: 0,
|
||
max: 5,
|
||
q: 5,
|
||
labels: List<String>.from(
|
||
sleepReport['dysp'][3]['yLable']
|
||
.map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-15.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildValueTexts(sleepReport['dysp'][3]['value'], '毫秒', 1),
|
||
xCount:
|
||
buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['dysp'][3]['yLable'].length,
|
||
points: buildGeneralPoints(sleepReport['dysp'][3]),
|
||
displayMode: ChartDisplayMode.line,
|
||
xUnit: sleepReport['dysp'][3]['yUnit'],
|
||
barWidth: 0.5,
|
||
bottomPadding: 16,
|
||
),
|
||
padding: 45.rpx),
|
||
TrendDataTablePage(
|
||
title: sleepReport['dysp'][4]['name'],
|
||
tipText: sleepReport['dysp'][4]['tips'],
|
||
chartContent: LineView(
|
||
xLabels: [
|
||
ChartLables(
|
||
//标签系列1
|
||
min: 0, //最小值0
|
||
max: buildMonthlyChartData(
|
||
sleepReport['scoreList'])['daysInMonth'], //最大值30
|
||
q: buildMonthlyChartData(sleepReport['scoreList'])[
|
||
'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29
|
||
labels: List<String>.from(
|
||
sleepReport['scoreList']['xLable'].map((e) => e['name']),
|
||
),
|
||
indexs: (sleepReport['scoreList']['xLable'] as List)
|
||
.map((e) => e['index'] as int)
|
||
.toList(),
|
||
offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style:
|
||
TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
yLabels: [
|
||
ChartLables(
|
||
min: 0,
|
||
max: 5,
|
||
q: 5,
|
||
labels: List<String>.from(
|
||
sleepReport['dysp'][4]['yLable']
|
||
.map((e) => e['name'].toString()),
|
||
),
|
||
offset: Offset(-15.rpx, 0),
|
||
ondrawer: (canvas, offset, index, label, align, style) {
|
||
ChartLables.drawText(
|
||
canvas,
|
||
label,
|
||
offset,
|
||
textAlign: TextAlign.center,
|
||
style: TextStyle(
|
||
color: Color(0xFFFFFFFF).withOpacity(0.6),
|
||
fontSize: 18.rpx),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
tips: buildValueTexts(sleepReport['dysp'][4]['value'], '次/分', 1),
|
||
xCount:
|
||
buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth']
|
||
.toInt(),
|
||
yCount: sleepReport['dysp'][4]['yLable'].length,
|
||
points: buildGeneralPoints(sleepReport['dysp'][4]),
|
||
displayMode: ChartDisplayMode.line,
|
||
xUnit: sleepReport['dysp'][4]['yUnit'],
|
||
barWidth: 0.5,
|
||
bottomPadding: 16,
|
||
),
|
||
padding: 45.rpx),
|
||
Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Text("MAC号:${data['mac']}",
|
||
style: TextStyle(
|
||
color: Color(0xFFD3D3D3).withOpacity(0.2), fontSize: 18.rpx))
|
||
],
|
||
),
|
||
]
|
||
.map((widget) => Padding(
|
||
padding: padding,
|
||
child: SizedBox(width: double.infinity, child: widget),
|
||
))
|
||
.toList();
|
||
}
|
||
|
||
return Column(
|
||
children: _buildSectionList(),
|
||
);
|
||
}
|
||
|
||
List<String> buildValueTexts(
|
||
List<dynamic> data,
|
||
String unit,
|
||
int direction, // 0 表示左侧,1 表示右侧
|
||
) {
|
||
if (data.isEmpty) return [];
|
||
|
||
return data
|
||
.where((item) => item.containsKey('value') && item.containsKey('st'))
|
||
.map((item) {
|
||
final val = item['value'].toString();
|
||
|
||
// 单位位置
|
||
final prefix = direction == 1 ? '' : unit;
|
||
final suffix = direction == 1 ? unit : '';
|
||
|
||
// 中文年月日格式
|
||
DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']);
|
||
final dateStr = "${date.year}年 ${date.month}月 ${date.day}日";
|
||
|
||
return "$prefix$val$suffix\n$dateStr";
|
||
}).toList();
|
||
}
|
||
|
||
List<String> buildSleepValueTexts(
|
||
List<dynamic> data,
|
||
String unit,
|
||
int direction, // 0 左侧,1 右侧
|
||
) {
|
||
if (data.isEmpty) return [];
|
||
|
||
return data.map((item) {
|
||
final dst = (item['dst'] ?? 0).toString();
|
||
final lst = (item['lst'] ?? 0).toString();
|
||
final slt = (item['slt'] ?? 0).toString();
|
||
|
||
final prefix = direction == 1 ? '' : unit;
|
||
final suffix = direction == 1 ? unit : '';
|
||
|
||
// 格式化日期(不带时间)
|
||
String dateStr = '';
|
||
if (item['st'] != null) {
|
||
final dt = DateTime.fromMillisecondsSinceEpoch(item['st']);
|
||
dateStr =
|
||
"${dt.year}年${dt.month.toString().padLeft(2, '0')}月${dt.day.toString().padLeft(2, '0')}日";
|
||
}
|
||
|
||
var q = [
|
||
"睡眠时长:$prefix$slt$suffix",
|
||
"深睡:$prefix$dst$suffix",
|
||
"浅睡:$prefix$lst$suffix",
|
||
dateStr,
|
||
].join("\n");
|
||
print(q);
|
||
return q;
|
||
}).toList();
|
||
}
|
||
|
||
Map<String, dynamic> buildMonthlyChartData(Map<String, dynamic> dyspData) {
|
||
final List<Map<String, dynamic>> data =
|
||
(dyspData['data'] as List?)?.whereType<Map<String, dynamic>>().toList() ??
|
||
[];
|
||
final List<Map<String, dynamic>> yLabels = (dyspData['yLable'] as List?)
|
||
?.whereType<Map<String, dynamic>>()
|
||
.toList() ??
|
||
[];
|
||
final List<Map<String, dynamic>> types =
|
||
(dyspData['type'] as List?)?.whereType<Map<String, dynamic>>().toList() ??
|
||
[];
|
||
|
||
if (data.isEmpty || yLabels.length < 2) {
|
||
return {
|
||
'points': <Offset>[],
|
||
'colors': <String>[],
|
||
'daysInMonth': 0.0,
|
||
};
|
||
}
|
||
|
||
// 解析 yLabel 为数值
|
||
List<double> yValues =
|
||
yLabels.map((e) => double.tryParse(e['name'].toString()) ?? 0).toList();
|
||
double minY = yValues.first;
|
||
double maxY = yValues.last;
|
||
double yRange = maxY - minY;
|
||
double yStepCount = (yValues.length - 1).toDouble(); // 👈 用 double
|
||
|
||
// 获取当前月的总天数(以第一条数据为准)
|
||
DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(data.first['st']);
|
||
double daysInMonth =
|
||
DateTime(baseDate.year, baseDate.month + 1, 0).day.toDouble() -
|
||
1; // 👈 改为 double
|
||
|
||
List<Offset> points = [];
|
||
List<String> colors = [];
|
||
|
||
for (var item in data) {
|
||
final int st = item['st'];
|
||
final int level = item['level'];
|
||
final double value = (item['value'] as num).toDouble();
|
||
|
||
DateTime date = DateTime.fromMillisecondsSinceEpoch(st);
|
||
double day = date.day.toDouble();
|
||
|
||
// x: day - 1(0 起始)
|
||
double x = day - 1;
|
||
|
||
// // y: 归一化
|
||
// double y = ((value - minY) / yRange) * yStepCount;
|
||
|
||
// 获取 color
|
||
String color = types.firstWhere(
|
||
(e) => e['level'] == level,
|
||
orElse: () => {'color': '#CCCCCC'},
|
||
)['color'];
|
||
|
||
points.add(Offset(x, value));
|
||
colors.add(color);
|
||
}
|
||
|
||
return {
|
||
'points': points,
|
||
'colors': colors,
|
||
'daysInMonth': daysInMonth,
|
||
};
|
||
}
|
||
|
||
List<Offset> buildGeneralPoints(Map<String, dynamic> dyspData) {
|
||
final values =
|
||
(dyspData['value'] as List?)?.whereType<Map<String, dynamic>>().toList();
|
||
final yLabels =
|
||
(dyspData['yLable'] as List?)?.whereType<Map<String, dynamic>>().toList();
|
||
if (values == null || values.isEmpty || yLabels == null || yLabels.length < 2)
|
||
return [];
|
||
|
||
bool isTimeAxis = yLabels.first['name'].toString().contains(":");
|
||
|
||
double labelToValue(String name) {
|
||
if (isTimeAxis) {
|
||
final parts = name.split(':');
|
||
int hour = int.parse(parts[0]);
|
||
int minute = int.parse(parts[1]);
|
||
return hour * 60 + minute * 1.0;
|
||
} else {
|
||
return double.tryParse(name) ?? 0;
|
||
}
|
||
}
|
||
|
||
List<double> labelValues =
|
||
yLabels.map((e) => labelToValue(e['name'])).toList();
|
||
|
||
// 处理时间轴跨午夜
|
||
bool crossMidnight = false;
|
||
if (isTimeAxis && labelValues.last < labelValues.first) {
|
||
crossMidnight = true;
|
||
double base = labelValues.first;
|
||
for (int i = 1; i < labelValues.length; i++) {
|
||
if (labelValues[i] < base) {
|
||
labelValues[i] += 1440; // +24小时,单位分钟
|
||
}
|
||
}
|
||
}
|
||
|
||
List<Offset> points = [];
|
||
|
||
for (var item in values) {
|
||
DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']);
|
||
double x = (date.day - 1).toDouble(); // 👈 按照“日”定位 X 坐标
|
||
|
||
dynamic rawValue = item['value'];
|
||
double valueMinBased;
|
||
|
||
if (isTimeAxis && rawValue is String) {
|
||
final parts = rawValue.split(':');
|
||
int hour = int.parse(parts[0]);
|
||
int minute = int.parse(parts[1]);
|
||
valueMinBased = hour * 60 + minute * 1.0;
|
||
|
||
if (crossMidnight && valueMinBased < labelValues.first) {
|
||
valueMinBased += 1440;
|
||
}
|
||
} else if (!isTimeAxis && rawValue is num) {
|
||
valueMinBased = rawValue.toDouble();
|
||
} else {
|
||
continue;
|
||
}
|
||
|
||
if (valueMinBased < labelValues.first) valueMinBased = labelValues.first;
|
||
if (valueMinBased > labelValues.last) valueMinBased = labelValues.last;
|
||
|
||
double y = getYPositionBySegmentedLabels(labelValues, valueMinBased);
|
||
|
||
points.add(Offset(x, y));
|
||
}
|
||
|
||
return points;
|
||
}
|
||
|
||
double getYPositionBySegmentedLabels(List<double> labels, double value) {
|
||
if (value <= labels.first) return 0.0;
|
||
if (value >= labels.last) return labels.length - 1.0;
|
||
|
||
for (int i = 0; i < labels.length - 1; i++) {
|
||
double low = labels[i];
|
||
double high = labels[i + 1];
|
||
if (value >= low && value <= high) {
|
||
double ratio = (value - low) / (high - low);
|
||
return i + ratio;
|
||
}
|
||
}
|
||
// 不应该走到这里,默认返回0
|
||
return 0.0;
|
||
}
|
||
|
||
//多色柱状图坐标
|
||
List<List<Offset>> buildTripleBarData(Map<String, dynamic> csd) {
|
||
final List<Map<String, dynamic>> data =
|
||
(csd['data'] as List?)?.whereType<Map<String, dynamic>>().toList() ?? [];
|
||
|
||
final List<Map<String, dynamic>> yLabels =
|
||
(csd['yLable'] as List?)?.whereType<Map<String, dynamic>>().toList() ??
|
||
[];
|
||
|
||
if (data.isEmpty || yLabels.isEmpty) return [];
|
||
|
||
double minY = double.tryParse(yLabels.first['name'].toString()) ?? 0;
|
||
double maxY = double.tryParse(yLabels.last['name'].toString()) ?? 10;
|
||
|
||
List<List<Offset>> points = [];
|
||
|
||
for (var item in data) {
|
||
double dst = (item['dst'] as num?)?.toDouble() ?? 0;
|
||
double lst = (item['lst'] as num?)?.toDouble() ?? 0;
|
||
double slt = (item['slt'] as num?)?.toDouble() ?? 0;
|
||
|
||
// 限制 y 值范围
|
||
dst = dst.clamp(minY, maxY);
|
||
lst = lst.clamp(minY, maxY);
|
||
slt = slt.clamp(minY, maxY);
|
||
|
||
// 计算 X:本月第几天(从 0 开始)
|
||
DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']);
|
||
int dayOfMonth = date.day; // 1 ~ 31
|
||
double x = (dayOfMonth - 1).toDouble(); // 转为从 0 开始
|
||
|
||
points.add([
|
||
Offset(x, dst), // 深睡终点
|
||
Offset(x, dst + lst), // 浅睡+深睡终点
|
||
Offset(x, slt), // 总睡眠终点(第三段)
|
||
]);
|
||
}
|
||
|
||
return points;
|
||
}
|