diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart index a567eeef..1cfab765 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_chart_widget.dart @@ -79,12 +79,17 @@ class _KlineChartWidgetState extends State { // 计算让所有 K 线占满屏幕的宽度 final idealWidth = chartWidth / widget.klines.length; - // 限制在合理范围内 - _candleWidth = idealWidth.clamp(_minCandleWidth, _maxCandleWidth); + // 限制在合理范围内,但如果数据太少,使用默认宽度而不是拉伸 + if (idealWidth > _maxCandleWidth) { + // K 线数量少,使用默认宽度(不要拉伸太宽) + _candleWidth = 8.0; + } else { + _candleWidth = idealWidth.clamp(_minCandleWidth, _maxCandleWidth); + } _prevCandleWidth = _candleWidth; - // 首次加载时让最新 K 线居中显示 - _scrollToCenter(); + // 首次加载时让最新 K 线靠右显示 + _scrollToEnd(); } /// 滚动使最新 K 线居中显示 @@ -390,6 +395,7 @@ class _KlineChartWidgetState extends State { crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1, candleWidth: visibleData.candleWidth, isDark: AppColors.isDark(context), + leftOffset: visibleData.leftOffset, ), ), // 十字线信息 @@ -430,6 +436,7 @@ class _KlineChartWidgetState extends State { crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1, candleWidth: visibleData.candleWidth, isDark: AppColors.isDark(context), + leftOffset: visibleData.leftOffset, ), ), ), @@ -570,6 +577,16 @@ class _KlineChartWidgetState extends State { return _VisibleData(klines: [], startIndex: 0, candleWidth: _candleWidth); } + // 计算K线总宽度(不包含左右padding) + const rightPadding = 50.0; // 与 painter 中的 rightPadding 保持一致 + const leftPadding = 8.0; // 与 painter 中的 leftPadding 保持一致 + final availableWidth = chartWidth - rightPadding - leftPadding; + final totalWidth = widget.klines.length * _candleWidth; + + // 计算左侧偏移量:当K线总宽度小于可用宽度时,将K线居中显示 + // 居中偏移 = (可用宽度 - K线总宽度) / 2 + final leftOffset = totalWidth < availableWidth ? (availableWidth - totalWidth) / 2 : 0.0; + // 根据滚动位置计算可见的K线范围 final int startIndex = (_scrollX / _candleWidth).floor().clamp(0, widget.klines.length - 1); final int visibleCount = (chartWidth / _candleWidth).ceil() + 1; // +1 确保边缘K线可见 @@ -579,6 +596,7 @@ class _KlineChartWidgetState extends State { klines: widget.klines.sublist(startIndex, endIndex), startIndex: startIndex, candleWidth: _candleWidth, + leftOffset: leftOffset, ); } @@ -771,6 +789,12 @@ class _VisibleData { final List klines; final int startIndex; final double candleWidth; + final double leftOffset; // K线数据稀疏时的左侧偏移,用于右对齐显示 - _VisibleData({required this.klines, required this.startIndex, required this.candleWidth}); + _VisibleData({ + required this.klines, + required this.startIndex, + required this.candleWidth, + this.leftOffset = 0.0, + }); } 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 666aae3a..0e62df32 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 @@ -12,6 +12,7 @@ class KlinePainter extends CustomPainter { final int crossLineIndex; final double? candleWidth; // 可选的K线宽度,不传则自动计算 final bool isDark; // 深色模式 + final double leftOffset; // K线稀疏时的左侧偏移,用于右对齐显示 static const Color _green = Color(0xFF10B981); static const Color _red = Color(0xFFEF4444); @@ -42,6 +43,7 @@ class KlinePainter extends CustomPainter { this.crossLineIndex = -1, this.candleWidth, this.isDark = false, + this.leftOffset = 0.0, }); @override @@ -137,7 +139,7 @@ class KlinePainter extends CustomPainter { final color = isUp ? _green : _red; final paint = Paint()..color = color; - final x = leftPadding + i * actualCandleWidth + actualCandleWidth / 2; + final x = leftPadding + leftOffset + i * actualCandleWidth + actualCandleWidth / 2; final yOpen = priceToY(open); final yClose = priceToY(close); final yHigh = priceToY(high); @@ -178,7 +180,7 @@ class KlinePainter extends CustomPainter { canvas, entry.value, _maColors[colorIndex % _maColors.length], - leftPadding, + leftPadding + leftOffset, actualCandleWidth, priceToY, ); @@ -194,7 +196,7 @@ class KlinePainter extends CustomPainter { canvas, entry.value, _maColors[colorIndex % _maColors.length], - leftPadding, + leftPadding + leftOffset, actualCandleWidth, priceToY, ); @@ -204,14 +206,14 @@ class KlinePainter extends CustomPainter { // 绘制BOLL线 if (bollData != null) { - _drawLine(canvas, bollData!['middle']!, _bollMiddleColor, leftPadding, actualCandleWidth, priceToY); - _drawLine(canvas, bollData!['upper']!, _bollUpperColor, leftPadding, actualCandleWidth, priceToY, isDashed: true); - _drawLine(canvas, bollData!['lower']!, _bollLowerColor, leftPadding, actualCandleWidth, priceToY, isDashed: true); + _drawLine(canvas, bollData!['middle']!, _bollMiddleColor, leftPadding + leftOffset, actualCandleWidth, priceToY); + _drawLine(canvas, bollData!['upper']!, _bollUpperColor, leftPadding + leftOffset, actualCandleWidth, priceToY, isDashed: true); + _drawLine(canvas, bollData!['lower']!, _bollLowerColor, leftPadding + leftOffset, actualCandleWidth, priceToY, isDashed: true); } // 绘制十字线 if (crossLineIndex >= 0 && crossLineIndex < klines.length) { - _drawCrossLine(canvas, size, crossLineIndex, leftPadding, actualCandleWidth, priceToY); + _drawCrossLine(canvas, size, crossLineIndex, leftPadding + leftOffset, actualCandleWidth, priceToY); } // 绘制MA图例 @@ -452,6 +454,7 @@ class KlinePainter extends CustomPainter { oldDelegate.bollData != bollData || oldDelegate.crossLineIndex != crossLineIndex || oldDelegate.candleWidth != candleWidth || - oldDelegate.isDark != isDark; + oldDelegate.isDark != isDark || + oldDelegate.leftOffset != leftOffset; } } diff --git a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart index f2155951..eed54ade 100644 --- a/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart +++ b/frontend/mining-app/lib/presentation/widgets/kline_chart/kline_volume_painter.dart @@ -9,6 +9,7 @@ class KlineVolumePainter extends CustomPainter { final int crossLineIndex; final double? candleWidth; final bool isDark; // 深色模式 + final double leftOffset; // K线稀疏时的左侧偏移,用于右对齐显示 static const Color _green = Color(0xFF10B981); static const Color _red = Color(0xFFEF4444); @@ -23,6 +24,7 @@ class KlineVolumePainter extends CustomPainter { this.crossLineIndex = -1, this.candleWidth, this.isDark = false, + this.leftOffset = 0.0, }); @override @@ -91,7 +93,7 @@ class KlineVolumePainter extends CustomPainter { final isUp = close >= open; final color = isUp ? _green : _red; - final x = leftPadding + i * actualCandleWidth + actualCandleWidth / 2; + final x = leftPadding + leftOffset + i * actualCandleWidth + actualCandleWidth / 2; final barHeight = (volume / maxVolume) * chartHeight; final y = size.height - bottomPadding - barHeight; @@ -106,7 +108,7 @@ class KlineVolumePainter extends CustomPainter { // 绘制十字线 if (crossLineIndex >= 0 && crossLineIndex < klines.length) { - final x = leftPadding + crossLineIndex * actualCandleWidth + actualCandleWidth / 2; + final x = leftPadding + leftOffset + crossLineIndex * actualCandleWidth + actualCandleWidth / 2; canvas.drawLine( Offset(x, 0), Offset(x, size.height), @@ -141,6 +143,7 @@ class KlineVolumePainter extends CustomPainter { return oldDelegate.klines != klines || oldDelegate.crossLineIndex != crossLineIndex || oldDelegate.candleWidth != candleWidth || - oldDelegate.isDark != isDark; + oldDelegate.isDark != isDark || + oldDelegate.leftOffset != leftOffset; } }