fix(kline): 稀疏K线数据时居中显示
当K线数量较少(如1分钟图刚开始)时,K线不再拉伸填满屏幕, 而是使用默认宽度并在可用区域内水平居中显示。 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f51aa44cd9
commit
f1b2ec7ede
|
|
@ -79,12 +79,17 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
|||
|
||||
// 计算让所有 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<KlineChartWidget> {
|
|||
crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1,
|
||||
candleWidth: visibleData.candleWidth,
|
||||
isDark: AppColors.isDark(context),
|
||||
leftOffset: visibleData.leftOffset,
|
||||
),
|
||||
),
|
||||
// 十字线信息
|
||||
|
|
@ -430,6 +436,7 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
|||
crossLineIndex: _showCrossLine ? _crossLineIndex - visibleData.startIndex : -1,
|
||||
candleWidth: visibleData.candleWidth,
|
||||
isDark: AppColors.isDark(context),
|
||||
leftOffset: visibleData.leftOffset,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -570,6 +577,16 @@ class _KlineChartWidgetState extends State<KlineChartWidget> {
|
|||
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<KlineChartWidget> {
|
|||
klines: widget.klines.sublist(startIndex, endIndex),
|
||||
startIndex: startIndex,
|
||||
candleWidth: _candleWidth,
|
||||
leftOffset: leftOffset,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -771,6 +789,12 @@ class _VisibleData {
|
|||
final List<Kline> 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,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue