Files
tuiche/lib/pages/sleep_report/component/WeekDataWidget.dart
2025-11-14 15:36:29 +08:00

1036 lines
37 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:ef/base/chart/drawer.dart';
import 'package:ef/ef.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/WeekSleepCard.dart';
import 'package:vbvs_app/pages/sleep_report/component/WeekSleepScoreWidget.dart';
Widget WeekDataWidget(
Map<dynamic, dynamic> sleepReport,
dynamic data,
) {
List<String> getDate() {
var s = buildWeekDatesAndPoints(sleepReport['scoreList']);
if (s == null ||
s['dates'] == null ||
s['dates'] is! List<String> ||
s['dates'].length != 7) {
return [];
}
return s['dates'];
}
List<Widget> _buildSectionList() {
EdgeInsetsDirectional padding =
EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 25.rpx);
return [
AvgSleepScoreWidget(
sleepReport: sleepReport,
),
SleepChartContainer(
title: "每日得分".tr,
tipText: "用户本周睡眠分数的汇总".tr,
sleepReport: sleepReport,
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: getDate(),
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style: TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.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),
);
},
),
],
xCount: 7,
yCount: sleepReport['scoreList']['yLable'].length,
points: buildWeekDatesAndPoints(sleepReport['scoreList'])['points'],
displayMode: ChartDisplayMode.bar,
barColors:
buildWeekDatesAndPoints(sleepReport['scoreList'])['colors'],
tips: buildValueTexts(sleepReport['scoreList']['data'], null, 1),
xUnit: sleepReport['scoreList']['yUnit'],
barWidth: 0.2,
),
showLabel: sleepReport['scoreList']['type'],
),
WeekSleepCard(
sleepReport: sleepReport,
highlightItem: data['itemName'],
),
IndicatorCompareCard(
title: "与上周对比".tr,
headers: ["名称".tr, "上周".tr, "本周".tr, "参考范围".tr],
tooltip: "睡眠分数与上周分数进行对比,是通过量化分析近期睡眠质量变化,可了解自身睡眠状态的波动情况,进而调整用户的作息习惯。".tr,
rows: (sleepReport['cwl'] ?? []).map<List<Widget>>((item) {
return [
Text(
item['name']?.toString() ?? '-',
style: TextStyle(
color: Color(0xFFFFFFFF), fontSize: 26.rpx, height: 1),
),
Text(
item['lastCurr']?.toString() ?? '-',
style: TextStyle(
color: Color(0xFFFFFFFF), fontSize: 26.rpx, height: 1),
),
Text(
item['curr']?.toString() ?? '-',
style: TextStyle(
color: Color(0xFFFFFFFF), fontSize: 26.rpx, height: 1),
),
Text(
item['range']?.toString() ?? '-',
style: TextStyle(
color: Color(0xFFFFFFFF), fontSize: 26.rpx, height: 1),
),
];
}).toList(),
),
SleepChartContainer(
title: "本周睡眠时长".tr,
tipText: "本周睡眠时长是指从周一到周日内,每天实际睡眠的时间总和。".tr,
sleepReport: sleepReport,
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels:
buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style: TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx),
);
},
),
],
yLabels: [
ChartLables(
min: sleepReport['csd']['yRange']['min'] ?? 0.0,
max: sleepReport['csd']['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),
);
},
),
],
xCount: 7,
yCount: sleepReport['csd']['yLable'].length,
points: [],
dualBarPoints: buildTripleBarData(sleepReport['csd']),
displayMode: ChartDisplayMode.dualBar,
xUnit: sleepReport['csd']['yUnit'],
tips: buildSleepValueTexts(
sleepReport['csd']['data'],
'小时'.tr,
1,
sleepDurationLabel: 'sleep_duration'.tr,
deepSleepLabel: 'deep_sleep'.tr,
lightSleepLabel: 'light_sleep'.tr,
),
barWidth: 0.2,
),
showLabel: sleepReport['csd']['type'],
),
TrendDataTablePage(
title: sleepReport['dysp'][0]['name'],
tipText: sleepReport['dysp'][0]['tips'],
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels:
buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style:
TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.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'], '入睡时间:'.tr, 0),
xCount: 7,
yCount: sleepReport['dysp'][0]['yLable'].length,
points: buildGeneralPoints(sleepReport['dysp'][0]),
displayMode: ChartDisplayMode.line,
xUnit: sleepReport['dysp'][0]['yUnit'],
barWidth: 0.1,
bottomPadding: 50,
),
padding: 45.rpx),
TrendDataTablePage(
title: sleepReport['dysp'][1]['name'],
tipText: sleepReport['dysp'][1]['tips'],
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels:
buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style:
TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.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'], '起床时间:'.tr, 0),
xCount: 7,
yCount: sleepReport['dysp'][1]['yLable'].length,
points: buildGeneralPoints(sleepReport['dysp'][1]),
displayMode: ChartDisplayMode.line,
xUnit: sleepReport['dysp'][1]['yUnit'],
barWidth: 0.1,
bottomPadding: 50,
),
padding: 45.rpx),
TrendDataTablePage(
title: sleepReport['dysp'][2]['name'],
tipText: sleepReport['dysp'][2]['tips'],
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels:
buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style:
TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.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'], ''.tr, 1),
xCount: 7,
yCount: sleepReport['dysp'][2]['yLable'].length,
points: buildGeneralPoints(sleepReport['dysp'][2]),
displayMode: ChartDisplayMode.line,
xUnit: sleepReport['dysp'][2]['yUnit'],
barWidth: 0.1,
bottomPadding: 50,
),
padding: 45.rpx),
TrendDataTablePage(
title: sleepReport['dysp'][3]['name'],
tipText: sleepReport['dysp'][3]['tips'],
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels:
buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style:
TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.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'], '毫秒'.tr, 1),
xCount: 7,
yCount: sleepReport['dysp'][3]['yLable'].length,
points: buildGeneralPoints(sleepReport['dysp'][3]),
displayMode: ChartDisplayMode.line,
xUnit: sleepReport['dysp'][3]['yUnit'],
barWidth: 0.1,
bottomPadding: 50,
),
padding: 45.rpx),
TrendDataTablePage(
title: sleepReport['dysp'][4]['name'],
tipText: sleepReport['dysp'][4]['tips'],
chartContent: LineView(
xLabels: [
ChartLables(
//标签系列1
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels:
buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
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),
);
},
),
ChartLables(
//X轴标签系列2
min: 0, //最小值0
max: 7, //最大值30
q: 6, //labels第一个与最后一个的真实距离也就是30-1 = 29
labels: [
'周一'.tr,
'周二'.tr,
'周三'.tr,
'周四'.tr,
'周五'.tr,
'周六'.tr,
'周日'.tr
],
indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置
offset: Offset(0, -50.rpx), //标签相对于原点的偏移下方60像素位置
ondrawer: (canvas, offset, index, label, align, style) {
ChartLables.drawText(
canvas,
label,
offset,
textAlign: TextAlign.center,
style:
TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx),
);
},
),
],
yLabels: [
ChartLables(
min: 0,
max: 5,
q: 5,
labels: List<String>.from(
sleepReport['dysp'][4]['yLable']
.map((e) => e['name'].toString()),
),
offset: Offset(-20.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'], '次/分'.tr, 1),
xCount: 7,
yCount: sleepReport['dysp'][4]['yLable'].length,
points: buildGeneralPoints(sleepReport['dysp'][4]),
displayMode: ChartDisplayMode.line,
xUnit: sleepReport['dysp'][4]['yUnit'],
barWidth: 0.1,
bottomPadding: 50,
),
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(),
);
}
// 计算非均匀标签的 y 坐标
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<String> buildValueTexts(
// List<dynamic> data,
// String unit,
// int direction, // 0 表示左侧1 表示右侧
// ) {
// if (data.isEmpty) return [];
// return data.where((item) => item.containsKey('value')).map((item) {
// final val = item['value'].toString();
// return direction == 1 ? "$val$unit" : "$unit$val";
// }).toList();
// }
List<String> buildValueTexts(
List<dynamic> data,
String? unit, // ✅ unit 改为可空类型
int direction, // 0 表示左侧1 表示右侧
) {
if (data.isEmpty) return [];
final safeUnit = unit ?? ""; // ✅ 防止 null
return data.where((item) => item.containsKey('value')).map((item) {
final val = item['value'].toString();
// ✅ 只有当 unit 不为空时才拼接
if (safeUnit.isEmpty) return val;
return direction == 1 ? "$val$safeUnit" : "$safeUnit$val";
}).toList();
}
//多个关键点标签
List<String> buildSleepValueTexts(
List<dynamic> data, String unit, int direction, // 0 左侧1 右侧
{required String sleepDurationLabel,
required String deepSleepLabel,
required String lightSleepLabel}) {
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 : '';
var lines = [
'$sleepDurationLabel$prefix$slt$suffix',
'$deepSleepLabel$prefix$dst$suffix',
'$lightSleepLabel$prefix$lst$suffix',
].join('\n');
return lines;
}).toList();
}
int getWeekdayX(DateTime current, DateTime first) {
final firstMonday = first.subtract(Duration(days: first.weekday - 1));
final currentMonday = current.subtract(Duration(days: current.weekday - 1));
final weekOffset = currentMonday.difference(firstMonday).inDays ~/ 7;
return weekOffset * 7 + (current.weekday - 1);
}
// Map<String, dynamic> buildWeekDatesAndPoints(Map<String, dynamic> scoreList) {
// if (scoreList == null ||
// !scoreList.containsKey('data') ||
// (scoreList['data'] as List).isEmpty) {
// return {
// 'dates': [],
// 'points': [],
// 'colors': [],
// };
// }
// List<Map<String, dynamic>> data = (scoreList['data'] as List)
// .where((item) => item is Map<String, dynamic>)
// .cast<Map<String, dynamic>>()
// .toList();
// // 提取 level -> color 映射表
// Map<int, String> levelColorMap = {};
// if (scoreList.containsKey('type')) {
// List typeList = scoreList['type'];
// for (var item in typeList) {
// if (item is Map &&
// item.containsKey('level') &&
// item.containsKey('color')) {
// levelColorMap[item['level']] = item['color'];
// }
// }
// }
// DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(data.first['st']);
// int weekday = baseDate.weekday;
// DateTime monday = baseDate.subtract(Duration(days: weekday - 1));
// List<String> dates = List.generate(7, (i) {
// DateTime d = monday.add(Duration(days: i));
// String month = d.month.toString().padLeft(2, '0');
// String day = d.day.toString().padLeft(2, '0');
// return "$month/$day";
// });
// List<Offset> points = [];
// List<String> colors = [];
// for (var item in data) {
// DateTime dt = DateTime.fromMillisecondsSinceEpoch(item['st']);
// double x = getWeekdayX(dt, baseDate).toDouble();
// double y = (item['value'] as num?)?.toDouble() ?? 0;
// int level = item['level'];
// String color = levelColorMap[level] ?? "#FFFF00";
// points.add(Offset(x, y));
// colors.add(color);
// }
// return {
// 'dates': dates,
// 'points': points,
// 'colors': colors,
// };
// }
Map<String, dynamic> buildWeekDatesAndPoints(Map<String, dynamic>? scoreList) {
try {
if (scoreList == null ||
!scoreList.containsKey('data') ||
(scoreList['data'] as List).isEmpty) {
return {
'dates': [],
'points': [],
'colors': [],
};
}
List<Map<String, dynamic>> data = (scoreList['data'] as List)
.where((item) => item is Map<String, dynamic>)
.cast<Map<String, dynamic>>()
.toList();
if (data.isEmpty) {
return {
'dates': [],
'points': [],
'colors': [],
};
}
// 提取 level -> color 映射表
Map<int, String> levelColorMap = {};
if (scoreList.containsKey('type')) {
List typeList = scoreList['type'];
for (var item in typeList) {
if (item is Map &&
item.containsKey('level') &&
item.containsKey('color')) {
levelColorMap[item['level']] = item['color'];
}
}
}
// 如果 st 字段异常,使用当前时间作为基准
int stValue = (data.first['st'] is int)
? data.first['st']
: DateTime.now().millisecondsSinceEpoch;
DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(stValue);
int weekday = baseDate.weekday;
DateTime monday = baseDate.subtract(Duration(days: weekday - 1));
List<String> dates = List.generate(7, (i) {
DateTime d = monday.add(Duration(days: i));
String month = d.month.toString().padLeft(2, '0');
String day = d.day.toString().padLeft(2, '0');
return "$month/$day";
});
List<Offset> points = [];
List<String> colors = [];
for (var item in data) {
if (item['st'] == null) continue;
DateTime dt = DateTime.fromMillisecondsSinceEpoch(item['st']);
double x = getWeekdayX(dt, baseDate).toDouble();
double y = (item['value'] as num?)?.toDouble() ?? 0;
int level = (item['level'] is int) ? item['level'] : 0;
String color = levelColorMap[level] ?? "#FFFF00";
points.add(Offset(x, y));
colors.add(color);
}
return {
'dates': dates,
'points': points,
'colors': colors,
};
} catch (e, s) {
// ⚠️ 捕获异常并返回空结构
debugPrint("❌ buildWeekDatesAndPoints 出错:$e");
debugPrint("—— 异常堆栈 ——");
debugPrintStack(stackTrace: s);
debugPrint("———————————");
return {
'dates': [],
'points': [],
'colors': [],
};
}
}
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;
}
}
}
DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(values.first['st']);
List<Offset> points = [];
for (var item in values) {
DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']);
double x = getWeekdayX(date, baseDate).toDouble();
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;
}
List<List<Offset>> buildTripleBarData(Map<String, dynamic> csd) {
final data =
(csd['data'] as List?)?.whereType<Map<String, dynamic>>().toList() ?? [];
final 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;
DateTime firstDate = DateTime.fromMillisecondsSinceEpoch(data.first['st']);
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;
dst = dst.clamp(minY, maxY);
lst = lst.clamp(minY, maxY);
slt = slt.clamp(minY, maxY);
DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']);
double x = getWeekdayX(date, firstDate).toDouble();
points.add([
Offset(x, dst),
Offset(x, dst + lst),
Offset(x, slt),
]);
}
return points;
}