|
...
|
...
|
@@ -515,6 +515,15 @@ class _HrvLineChartState extends State<_HrvLineChart> { |
|
|
|
Timer? _hrvTooltipTimer;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void didUpdateWidget(covariant _HrvLineChart oldWidget) {
|
|
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
if (widget.spots != oldWidget.spots) {
|
|
|
|
_hrvTooltipTimer?.cancel();
|
|
|
|
_hrvShowingSpots = [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_hrvTooltipTimer?.cancel();
|
|
|
|
super.dispose();
|
|
...
|
...
|
@@ -614,6 +623,8 @@ class _HrvLineChartState extends State<_HrvLineChart> { |
|
|
|
ShowingTooltipIndicators(_hrvShowingSpots),
|
|
|
|
],
|
|
|
|
lineTouchData: LineTouchData(
|
|
|
|
getTouchLineStart: (barData, spotIndex) => 0,
|
|
|
|
getTouchLineEnd: (barData, spotIndex) => 90,
|
|
|
|
handleBuiltInTouches: false,
|
|
|
|
touchSpotThreshold: 20.0,
|
|
|
|
touchCallback:
|
|
...
|
...
|
@@ -673,6 +684,8 @@ class _HrvLineChartState extends State<_HrvLineChart> { |
|
|
|
},
|
|
|
|
touchTooltipData: LineTouchTooltipData(
|
|
|
|
tooltipRoundedRadius: 8,
|
|
|
|
showOnTopOfTheChartBoxArea: true,
|
|
|
|
tooltipMargin: 6,
|
|
|
|
tooltipBorder: BorderSide.none,
|
|
|
|
maxContentWidth: 400,
|
|
|
|
fitInsideHorizontally: true,
|
|
...
|
...
|
@@ -737,6 +750,18 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
Timer? _stressTooltipTimer;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void didUpdateWidget(covariant _StressBarChart oldWidget) {
|
|
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
if (widget.selectedDate != oldWidget.selectedDate ||
|
|
|
|
widget.stressPoints != oldWidget.stressPoints) {
|
|
|
|
_stressTooltipTimer?.cancel();
|
|
|
|
setState(() {
|
|
|
|
_stressTouchedGroupIndex = null;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_stressTooltipTimer?.cancel();
|
|
|
|
super.dispose();
|
|
...
|
...
|
@@ -749,12 +774,14 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
// 右轴 reservedSize=22,不占用空间设为 0.0
|
|
|
|
const rightAxisWidth = 0.0;
|
|
|
|
const slotSec = 6 * 60;
|
|
|
|
final slotCount = (widget.maxSeconds / slotSec).ceil().clamp(1, 9999);
|
|
|
|
// 关键:由于循环条件是 sec <= maxSec,实际绘制的组数(groups.length)为总槽数 + 1
|
|
|
|
final slotCount = (widget.maxSeconds / slotSec).round() + 1;
|
|
|
|
const double groupsSpace = 0.25; // 设置间隔为 1 像素
|
|
|
|
final barWidth = (constraints.maxWidth -
|
|
|
|
rightAxisWidth -
|
|
|
|
(groupsSpace * (slotCount - 1))) /
|
|
|
|
slotCount;
|
|
|
|
// 因为 BarChart 外层 Container 有 padding: EdgeInsets.only(right: 3),所以其实际宽度减少了 3.0
|
|
|
|
final chartWidth = constraints.maxWidth - 3.0;
|
|
|
|
final barWidth =
|
|
|
|
(chartWidth - rightAxisWidth - (groupsSpace * (slotCount - 1))) /
|
|
|
|
slotCount;
|
|
|
|
// 根据 V2SleepTimeRange 计算多段睡眠区间的 Positioned widgets
|
|
|
|
final sleepWidgets = <Widget>[];
|
|
|
|
|
|
...
|
...
|
@@ -873,6 +900,25 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
final gridBottom = chartHeight - 20.0;
|
|
|
|
final gridHeight = gridBottom - gridTop;
|
|
|
|
|
|
|
|
// 自定义指示线 (只在 Stack 层手绘一条垂直灰色指示线,与柱子对齐,高度到达顶部以与 tooltip 连上)
|
|
|
|
Positioned? customIndicator;
|
|
|
|
if (_stressTouchedGroupIndex != null) {
|
|
|
|
final stepWidth = barWidth + groupsSpace;
|
|
|
|
final centerOfBar =
|
|
|
|
_stressTouchedGroupIndex! * stepWidth + (barWidth / 2);
|
|
|
|
customIndicator = Positioned(
|
|
|
|
left: centerOfBar - 1.0, // 2px 宽度,居中
|
|
|
|
width: 2.0,
|
|
|
|
top: 16.0, // 向上延展以连接 tooltip 底部
|
|
|
|
bottom: chartHeight - gridBottom, // 修正:bottom 指的是距离 Stack 底部的像素值
|
|
|
|
child: IgnorePointer(
|
|
|
|
child: ColoredBox(
|
|
|
|
color: context.colors.textTertiary.withAlpha(125),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Stack(
|
|
|
|
clipBehavior: Clip.none,
|
|
|
|
children: [
|
|
...
|
...
|
@@ -1039,7 +1085,9 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
tooltipBorder: BorderSide.none,
|
|
|
|
maxContentWidth: 400,
|
|
|
|
fitInsideHorizontally: true,
|
|
|
|
fitInsideVertically: false,
|
|
|
|
fitInsideVertically:
|
|
|
|
false, // 允许 tooltip 超出 BarChart 顶边界(绘制在 18px 的 top margin 区域内)
|
|
|
|
tooltipMargin: 6, // 调整为和折线图一样的 6 像素空隙
|
|
|
|
getTooltipColor: (touchedSpot) => const Color(0xFFF3F3F3),
|
|
|
|
tooltipPadding: const EdgeInsets.only(
|
|
|
|
left: 12,
|
|
...
|
...
|
@@ -1057,7 +1105,8 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
color: Colors.white,
|
|
|
|
fontWeight: FontWeight.bold,
|
|
|
|
),
|
|
|
|
children: widget.getTooltip(rod, group.x, state),
|
|
|
|
children: widget.getTooltip(
|
|
|
|
group.barRods[0], group.x, state),
|
|
|
|
textAlign: TextAlign.start,
|
|
|
|
);
|
|
|
|
},
|
|
...
|
...
|
@@ -1066,6 +1115,8 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
if (customIndicator != null)
|
|
|
|
customIndicator, // 将指示线移动到 Container (BarChart) 之后绘制,防止被盖住
|
|
|
|
...sleepWidgets,
|
|
|
|
// 手绘 100、50、0 标签在对应的横线上方
|
|
|
|
Positioned(
|
|
...
|
...
|
@@ -1126,7 +1177,8 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
final isTouched = groupIndex == _stressTouchedGroupIndex;
|
|
|
|
groups.add(BarChartGroupData(
|
|
|
|
x: sec,
|
|
|
|
showingTooltipIndicators: isTouched ? [0] : [],
|
|
|
|
barsSpace: 0,
|
|
|
|
showingTooltipIndicators: isTouched ? [1] : [],
|
|
|
|
barRods: [
|
|
|
|
BarChartRodData(
|
|
|
|
toY: entry != null ? entry.value.toDouble() : 0,
|
|
...
|
...
|
@@ -1136,6 +1188,11 @@ class _StressBarChartState extends State<_StressBarChart> { |
|
|
|
: Colors.transparent,
|
|
|
|
borderRadius: BorderRadius.circular(barWidth / 2),
|
|
|
|
),
|
|
|
|
BarChartRodData(
|
|
|
|
toY: 100, // 用于固定 tooltip 在顶部
|
|
|
|
width: 0, // 0 宽度,不占空间
|
|
|
|
color: Colors.transparent,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
));
|
|
|
|
}
|
...
|
...
|
|