From 900ba4a55507833265b3f2c8e829b0ed2c33785b Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 19 Jan 2026 21:51:23 -0800 Subject: [PATCH] feat(kline): add dynamic X-axis time labels - Added _drawTimeAxis method to render time labels - Labels dynamically adjust spacing based on candleWidth - Shows HH:MM format, or M/D for midnight - Labels follow pan/zoom movements - Increased bottomPadding to 20px for label space Co-Authored-By: Claude Opus 4.5 --- .../widgets/kline_chart/kline_painter.dart | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart index 54e6723a..67b7eba4 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_painter.dart @@ -54,7 +54,7 @@ class KlinePainter extends CustomPainter { const leftPadding = 8.0; const rightPadding = 50.0; const topPadding = 20.0; - const bottomPadding = 8.0; + const bottomPadding = 20.0; // 增加底部空间用于时间刻度 final chartWidth = size.width - leftPadding - rightPadding; final chartHeight = size.height - topPadding - bottomPadding; @@ -235,6 +235,9 @@ class KlinePainter extends CustomPainter { _drawCrossLine(canvas, size, crossLineIndex, leftPadding, actualCandleWidth, priceToY, scrollOffset); } + // 绘制时间轴 + _drawTimeAxis(canvas, size, leftPadding, rightPadding, bottomPadding, actualCandleWidth, scrollOffset); + // 绘制MA图例 _drawLegend(canvas, size, leftPadding, topPadding); } @@ -280,6 +283,77 @@ class KlinePainter extends CustomPainter { } } + void _drawTimeAxis( + Canvas canvas, + Size size, + double leftPadding, + double rightPadding, + double bottomPadding, + double candleWidth, + double scrollOffset, + ) { + if (klines.isEmpty) return; + + final textPainter = TextPainter(textDirection: ui.TextDirection.ltr); + final drawableWidth = size.width - leftPadding - rightPadding; + + // 根据K线宽度动态计算时间标签间隔 + // 目标:标签之间至少间隔60像素 + final minLabelSpacing = 60.0; + final klinePerLabel = (minLabelSpacing / candleWidth).ceil(); + // 确保间隔是合理的数字(5, 10, 15, 20, 30, 60等) + final intervals = [5, 10, 15, 20, 30, 60, 120, 240]; + int labelInterval = intervals.firstWhere( + (i) => i >= klinePerLabel, + orElse: () => klinePerLabel, + ); + + // 计算可见区域的起始和结束索引 + final startIndex = (scrollOffset / candleWidth).floor(); + final endIndex = ((scrollOffset + drawableWidth) / candleWidth).ceil(); + + // 找到第一个应该显示标签的索引(对齐到间隔) + final firstLabelIndex = ((startIndex / labelInterval).ceil()) * labelInterval; + + final y = size.height - bottomPadding + 4; + + for (int i = firstLabelIndex; i <= endIndex && i < klines.length; i += labelInterval) { + if (i < 0) continue; + + final x = leftPadding + i * candleWidth + candleWidth / 2 - scrollOffset; + + // 检查是否在可见区域内 + if (x < leftPadding - 20 || x > size.width - rightPadding + 20) continue; + + final time = klines[i].time; + final timeStr = _formatTimeLabel(time); + + textPainter.text = TextSpan( + text: timeStr, + style: TextStyle(color: _textColor, fontSize: 9), + ); + textPainter.layout(); + + // 居中绘制时间标签 + final textX = x - textPainter.width / 2; + // 确保不超出边界 + final clampedX = textX.clamp(leftPadding, size.width - rightPadding - textPainter.width); + textPainter.paint(canvas, Offset(clampedX, y)); + } + } + + String _formatTimeLabel(DateTime time) { + // 根据时间显示不同格式 + // 如果是新的一天,显示日期;否则显示时间 + final hour = time.hour.toString().padLeft(2, '0'); + final minute = time.minute.toString().padLeft(2, '0'); + if (time.hour == 0 && time.minute == 0) { + // 新的一天,显示月/日 + return '${time.month}/${time.day}'; + } + return '$hour:$minute'; + } + void _drawLine( Canvas canvas, List data,