修复日报中图标时间轴错乱。
This commit is contained in:
@@ -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);
|
||||
|
||||
// 第一个刻度,原始 startTime,HH: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));
|
||||
}
|
||||
|
||||
// 最后一个刻度,原始 endTime,HH: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 {
|
||||
// 中间显示小时H,24小时制,不补零
|
||||
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});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user