import 'package:flutter/material.dart'; import '../entity/candle_entity.dart'; import '../k_chart_widget.dart' show MainState; import 'base_chart_renderer.dart'; enum VerticalTextAlignment { left, right } //For TrendLine double? trendLineMax; double? trendLineScale; double? trendLineContentRec; class MainRenderer extends BaseChartRenderer { late double mCandleWidth; late double mCandleLineWidth; List stateLi; bool isLine; //绘制的内容区域 late Rect _contentRect; double _contentPadding = 5.0; List maDayList; final ChartStyle chartStyle; final ChartColors chartColors; final double mLineStrokeWidth = 1.0; double scaleX; late Paint mLinePaint; final VerticalTextAlignment verticalTextAlignment; MainRenderer( Rect mainRect, double maxValue, double minValue, double topPadding, this.stateLi, this.isLine, int fixedLength, this.chartStyle, this.chartColors, this.scaleX, this.verticalTextAlignment, [this.maDayList = const [5, 10, 20]]) : super( chartRect: mainRect, maxValue: maxValue, minValue: minValue, topPadding: topPadding, fixedLength: fixedLength, gridColor: chartColors.gridColor) { mCandleWidth = this.chartStyle.candleWidth; mCandleLineWidth = this.chartStyle.candleLineWidth; mLinePaint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke ..strokeWidth = mLineStrokeWidth ..color = this.chartColors.kLineColor; _contentRect = Rect.fromLTRB( chartRect.left, chartRect.top + _contentPadding, chartRect.right, chartRect.bottom - _contentPadding); if (maxValue == minValue) { maxValue *= 1.5; minValue /= 2; } scaleY = _contentRect.height / (maxValue - minValue); } @override void drawText(Canvas canvas, CandleEntity data, double x) { if (isLine == true) return; for (int i = 0; i < stateLi.length; ++i) { TextSpan? span; if (stateLi[i] == MainState.MA) { span = TextSpan( children: _createMATextSpan(data), ); } else if (stateLi[i] == MainState.BOLL) { span = TextSpan( children: [ if (data.up != 0) TextSpan( text: "BOLL:${format(data.mb)} ", style: getTextStyle(this.chartColors.ma5Color)), if (data.mb != 0) TextSpan( text: "UB:${format(data.up)} ", style: getTextStyle(this.chartColors.ma10Color)), if (data.dn != 0) TextSpan( text: "LB:${format(data.dn)} ", style: getTextStyle(this.chartColors.ma30Color)), ], ); } else if (stateLi[i] == MainState.SAR) { span = TextSpan( text: "SAR:${format(data.sar)}", style: getTextStyle(this.chartColors.sarColor), ); } if (span == null) return; TextPainter tp = TextPainter(text: span, textDirection: TextDirection.ltr); tp.layout(); Offset offset = Offset(x, chartRect.top - topPadding + i * 12); canvas.drawRect( Rect.fromLTRB( offset.dx - 2, offset.dy - 2, tp.width + offset.dx + 2, tp.height + offset.dy + 2, ), Paint()..color = this.chartColors.bgColor); tp.paint(canvas, offset); } } List _createMATextSpan(CandleEntity data) { List result = []; for (int i = 0; i < (data.maValueList?.length ?? 0); i++) { if (data.maValueList?[i] != 0) { var item = TextSpan( text: "MA${maDayList[i]}:${format(data.maValueList![i])} ", style: getTextStyle(this.chartColors.getMAColor(i))); result.add(item); } } return result; } @override void drawChart(CandleEntity lastPoint, CandleEntity curPoint, double lastX, double curX, Size size, Canvas canvas) { if (isLine) { drawPolyline(lastPoint.close, curPoint.close, canvas, lastX, curX); } else { drawCandle(curPoint, canvas, curX); /// draw chart main state for (int i = 0; i < stateLi.length; ++i) { if (stateLi[i] == MainState.MA) { drawMaLine(lastPoint, curPoint, canvas, lastX, curX); } else if (stateLi[i] == MainState.BOLL) { drawBollLine(lastPoint, curPoint, canvas, lastX, curX); } else if (stateLi[i] == MainState.SAR) { drawSAR(lastPoint, curPoint, canvas, lastX, curX); } } } } Shader? mLineFillShader; Path? mLinePath, mLineFillPath; Paint mLineFillPaint = Paint() ..style = PaintingStyle.fill ..isAntiAlias = true; //画折线图 drawPolyline(double lastPrice, double curPrice, Canvas canvas, double lastX, double curX) { // drawLine(lastPrice + 100, curPrice + 100, canvas, lastX, curX, ChartColors.kLineColor); mLinePath ??= Path(); // if (lastX == curX) { // mLinePath.moveTo(lastX, getY(lastPrice)); // } else { //// mLinePath.lineTo(curX, getY(curPrice)); // mLinePath.cubicTo( // (lastX + curX) / 2, getY(lastPrice), (lastX + curX) / 2, getY(curPrice), curX, getY(curPrice)); // } if (lastX == curX) lastX = 0; //起点位置填充 mLinePath!.moveTo(lastX, getY(lastPrice)); mLinePath!.cubicTo((lastX + curX) / 2, getY(lastPrice), (lastX + curX) / 2, getY(curPrice), curX, getY(curPrice)); //画阴影 mLineFillShader ??= LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, tileMode: TileMode.clamp, colors: [ this.chartColors.lineFillColor, this.chartColors.lineFillInsideColor ], ).createShader(Rect.fromLTRB( chartRect.left, chartRect.top, chartRect.right, chartRect.bottom)); mLineFillPaint..shader = mLineFillShader; mLineFillPath ??= Path(); mLineFillPath!.moveTo(lastX, chartRect.height + chartRect.top); mLineFillPath!.lineTo(lastX, getY(lastPrice)); mLineFillPath!.cubicTo((lastX + curX) / 2, getY(lastPrice), (lastX + curX) / 2, getY(curPrice), curX, getY(curPrice)); mLineFillPath!.lineTo(curX, chartRect.height + chartRect.top); mLineFillPath!.close(); canvas.drawPath(mLineFillPath!, mLineFillPaint); mLineFillPath!.reset(); canvas.drawPath(mLinePath!, mLinePaint..strokeWidth = (mLineStrokeWidth / scaleX).clamp(0.1, 1.0)); mLinePath!.reset(); } void drawMaLine(CandleEntity lastPoint, CandleEntity curPoint, Canvas canvas, double lastX, double curX) { for (int i = 0; i < (curPoint.maValueList?.length ?? 0); i++) { if (i == 3) { break; } if (lastPoint.maValueList?[i] != 0) { drawLine(lastPoint.maValueList?[i], curPoint.maValueList?[i], canvas, lastX, curX, this.chartColors.getMAColor(i)); } } } void drawBollLine(CandleEntity lastPoint, CandleEntity curPoint, Canvas canvas, double lastX, double curX) { if (lastPoint.up != 0) { drawLine(lastPoint.up, curPoint.up, canvas, lastX, curX, this.chartColors.ma10Color); } if (lastPoint.mb != 0) { drawLine(lastPoint.mb, curPoint.mb, canvas, lastX, curX, this.chartColors.ma5Color); } if (lastPoint.dn != 0) { drawLine(lastPoint.dn, curPoint.dn, canvas, lastX, curX, this.chartColors.ma30Color); } } void drawSAR(CandleEntity lastPoint, CandleEntity curPoint, Canvas canvas, double lastX, double curX) { final sar = curPoint.sar; if (sar == null) return; final halfHL = (curPoint.high + curPoint.low) / 2; late final color; if (sar == halfHL) { color = this.chartColors.avgColor; } else if (sar < halfHL) { color = this.chartColors.upColor; } else { color = this.chartColors.dnColor; } drawCircle(canvas, curX, sar, color); } void drawCandle(CandleEntity curPoint, Canvas canvas, double curX) { var high = getY(curPoint.high); var low = getY(curPoint.low); var open = getY(curPoint.open); var close = getY(curPoint.close); double r = mCandleWidth / 2; double lineR = mCandleLineWidth / 2; if (open >= close) { // 实体高度>= CandleLineWidth if (open - close < mCandleLineWidth) { open = close + mCandleLineWidth; } chartPaint.color = this.chartColors.upColor; canvas.drawRect( Rect.fromLTRB(curX - r, close, curX + r, open), chartPaint); canvas.drawRect( Rect.fromLTRB(curX - lineR, high, curX + lineR, low), chartPaint); } else if (close > open) { // 实体高度>= CandleLineWidth if (close - open < mCandleLineWidth) { open = close - mCandleLineWidth; } chartPaint.color = this.chartColors.dnColor; canvas.drawRect( Rect.fromLTRB(curX - r, open, curX + r, close), chartPaint); canvas.drawRect( Rect.fromLTRB(curX - lineR, high, curX + lineR, low), chartPaint); } } @override void drawVerticalText(canvas, textStyle, int gridRows) { double rowSpace = chartRect.height / gridRows; for (var i = 0; i <= gridRows; ++i) { double value = (gridRows - i) * rowSpace / scaleY + minValue; TextSpan span = TextSpan(text: "${format(value)}", style: textStyle); TextPainter tp = TextPainter(text: span, textDirection: TextDirection.ltr); tp.layout(); double offsetX; switch (verticalTextAlignment) { case VerticalTextAlignment.left: offsetX = 0; break; case VerticalTextAlignment.right: offsetX = chartRect.width - tp.width; break; } if (i == 0) { tp.paint(canvas, Offset(offsetX, topPadding)); } else { tp.paint( canvas, Offset(offsetX, rowSpace * i - tp.height + topPadding)); } } } @override void drawGrid(Canvas canvas, int gridRows, int gridColumns) { // final int gridRows = 4, gridColumns = 4; double rowSpace = chartRect.height / gridRows; for (int i = 0; i <= gridRows; i++) { canvas.drawLine(Offset(0, rowSpace * i + topPadding), Offset(chartRect.width, rowSpace * i + topPadding), gridPaint); } double columnSpace = chartRect.width / gridColumns; for (int i = 0; i <= columnSpace; i++) { canvas.drawLine(Offset(columnSpace * i, 0), Offset(columnSpace * i, chartRect.bottom), gridPaint); } } @override double getY(double y) { //For TrendLine updateTrendLineData(); return (maxValue - y) * scaleY + _contentRect.top; } void updateTrendLineData() { trendLineMax = maxValue; trendLineScale = scaleY; trendLineContentRec = _contentRect.top; } }