修复日报中图标时间轴错乱。

This commit is contained in:
wyf
2026-01-08 11:56:17 +08:00
parent 575f91e8dd
commit 2991deb8b3
21 changed files with 2824 additions and 1055 deletions

View File

@@ -25,63 +25,113 @@ class TimeSeriesChart extends StatelessWidget {
required this.dataPoints,
}) : super(key: key);
// X轴刻度数据
List<XLabel> _generateXLabels() {
final labels = <XLabel>[];
// 计算总分钟数
double get _totalMinutes {
return (endTime - startTime) / (1000 * 60);
}
// 生成X轴刻度标签
Map<double, String> _generateXLabels() {
final labels = <double, String>{};
final startDate = DateTime.fromMillisecondsSinceEpoch(startTime);
final endDate = DateTime.fromMillisecondsSinceEpoch(endTime);
// 第一个刻度,原始 startTimeHH:mm格式
labels.add(XLabel(
time: startTime,
label:
'${startDate.hour.toString().padLeft(2, '0')}:${startDate.minute.toString().padLeft(2, '0')}',
));
// 0分钟位置起始时间
labels[0.0] =
'${startDate.hour.toString().padLeft(2, '0')}:${startDate.minute.toString().padLeft(2, '0')}';
// 生成中间整点小时刻度,注意起点向上取整一个小时
DateTime current = DateTime(
startDate.year,
startDate.month,
startDate.day,
startDate.hour,
);
if (startDate.minute > 0 ||
startDate.second > 0 ||
startDate.millisecond > 0) {
// 如果 startTime 不是整点,跳到下一个整点小时
current = current.add(Duration(hours: 1));
// 计算总小时数
final int hourMs = 60 * 60 * 1000;
final int totalHours = (endTime - startTime) ~/ hourMs;
// 按照参考代码的逻辑当小时数超过8时跳着显示
if (totalHours <= 8) {
// 显示每个整点
DateTime currentHour = DateTime(
startDate.year,
startDate.month,
startDate.day,
startDate.hour,
);
// 如果起始时间不是整点,从下一个整点开始
if (startDate.minute > 0 ||
startDate.second > 0 ||
startDate.millisecond > 0) {
currentHour = currentHour.add(Duration(hours: 1));
}
while (currentHour.millisecondsSinceEpoch < endTime) {
final int timeMs = currentHour.millisecondsSinceEpoch;
if (timeMs > startTime && timeMs < endTime) {
// 检查是否太靠近边界
if (timeMs - startTime < 10 * 60 * 1000 ||
endTime - timeMs < 10 * 60 * 1000) {
currentHour = currentHour.add(Duration(hours: 1));
continue;
}
// 计算从起始时间到当前整点的分钟数
final minutesFromStart =
(currentHour.millisecondsSinceEpoch - startTime) / (1000 * 60);
labels[minutesFromStart] = '${currentHour.hour}';
}
currentHour = currentHour.add(Duration(hours: 1));
}
} else {
// 超过8小时跳着显示
final int labelInterval = (totalHours / 6).ceil(); // 分成大约6个标签
DateTime firstLabelHour = DateTime(
startDate.year,
startDate.month,
startDate.day,
startDate.hour + (labelInterval - (startDate.hour % labelInterval)),
0,
0,
0,
0,
);
// 确保第一个标签在开始时间之后
if (firstLabelHour.millisecondsSinceEpoch <= startTime) {
firstLabelHour = firstLabelHour.add(Duration(hours: labelInterval));
}
DateTime currentHour = firstLabelHour;
while (currentHour.millisecondsSinceEpoch < endTime) {
final int timeMs = currentHour.millisecondsSinceEpoch;
// 确保标签离边界足够远
if (timeMs - startTime >= hourMs && endTime - timeMs >= hourMs) {
final minutesFromStart =
(currentHour.millisecondsSinceEpoch - startTime) / (1000 * 60);
labels[minutesFromStart] = '${currentHour.hour}';
}
currentHour = currentHour.add(Duration(hours: labelInterval));
}
}
while (current.isBefore(endDate)) {
labels.add(XLabel(
time: current.millisecondsSinceEpoch,
label: current.hour.toString(),
));
current = current.add(Duration(hours: 1));
}
// 最后一个刻度,原始 endTimeHH:mm格式
labels.add(XLabel(
time: endTime,
label:
'${endDate.hour.toString().padLeft(2, '0')}:${endDate.minute.toString().padLeft(2, '0')}',
));
// 最后位置:结束时间
labels[_totalMinutes] =
'${endDate.hour.toString().padLeft(2, '0')}:${endDate.minute.toString().padLeft(2, '0')}';
return labels;
}
// 时间戳映射到0~(labels.length-1)之间
double _timeToX(double timestamp, List<XLabel> labels) {
int start = labels.first.time;
int end = labels.last.time;
double total = (end - start).toDouble();
double pos = (timestamp - start).clamp(0, total).toDouble();
return pos / total * (labels.length - 1);
// 时间戳映射到X坐标分钟数
double _timeToX(double timestamp) {
final minutesFromStart = (timestamp - startTime) / (1000 * 60);
return minutesFromStart.clamp(0.0, _totalMinutes);
}
@override
Widget build(BuildContext context) {
final xLabels = _generateXLabels();
final labelPositions = xLabels.keys.toList()..sort();
final midY = (yMin + yMax) / 2;
// 将数据点分割成多个连续段遇到value=-1时断开
@@ -92,7 +142,7 @@ class TimeSeriesChart extends StatelessWidget {
if (point.value != -1) {
// 有效数据点,添加到当前段
currentSegment.add(FlSpot(
_timeToX(point.timestamp.toDouble(), xLabels),
_timeToX(point.timestamp.toDouble()),
point.value,
));
} else if (currentSegment.isNotEmpty) {
@@ -112,7 +162,7 @@ class TimeSeriesChart extends StatelessWidget {
child: LineChart(
LineChartData(
minX: 0,
maxX: (xLabels.length - 1).toDouble(),
maxX: _totalMinutes,
minY: yMin < 0 ? yMin : 0,
maxY: yMax,
gridData: FlGridData(show: false),
@@ -144,37 +194,26 @@ class TimeSeriesChart extends StatelessWidget {
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1,
interval: 1, // 现在每个单位是1分钟
getTitlesWidget: (value, meta) {
int index = value.toInt();
if (index < 0 || index >= xLabels.length)
return const SizedBox.shrink();
// 四舍五入到最接近的整数
final roundedValue = value.roundToDouble();
final dateTime =
DateTime.fromMillisecondsSinceEpoch(xLabels[index].time);
if (index == 0 || index == xLabels.length - 1) {
// 开始和结束显示 HH:mm
final formatted =
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(formatted,
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: 16.rpx)),
);
} else {
// 中间显示小时H24小时制不补零
final formatted = '${dateTime.hour}';
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(formatted,
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: 16.rpx)),
);
// 检查是否在标签位置
for (var position in labelPositions) {
if ((position - roundedValue).abs() < 0.5) {
final label = xLabels[position] ?? '';
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(label,
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: 16.rpx)),
);
}
}
return const SizedBox.shrink();
},
),
),
@@ -238,9 +277,3 @@ class TimeSeriesChart extends StatelessWidget {
);
}
}
class XLabel {
final int time;
final String label;
XLabel({required this.time, required this.label});
}