917 lines
33 KiB
Dart
917 lines
33 KiB
Dart
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/WeekSleepScoreWidget.dart';
|
||
|
||
Widget WeekDataWidget(
|
||
Map<dynamic, dynamic> sleepReport,
|
||
dynamic data,
|
||
) {
|
||
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:
|
||
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['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'], '分', 1),
|
||
xUnit: sleepReport['scoreList']['yUnit'],
|
||
barWidth: 0.2,
|
||
),
|
||
showLabel: sleepReport['scoreList']['type'],
|
||
),
|
||
SleepCard(
|
||
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),
|
||
),
|
||
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: "本周睡眠时长".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> 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.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,
|
||
};
|
||
}
|
||
|
||
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;
|
||
}
|