main_renderer.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import 'package:flutter/material.dart';
  2. import '../entity/candle_entity.dart';
  3. import '../k_chart_widget.dart' show MainState;
  4. import 'base_chart_renderer.dart';
  5. enum VerticalTextAlignment { left, right }
  6. //For TrendLine
  7. double? trendLineMax;
  8. double? trendLineScale;
  9. double? trendLineContentRec;
  10. class MainRenderer extends BaseChartRenderer<CandleEntity> {
  11. late double mCandleWidth;
  12. late double mCandleLineWidth;
  13. List<MainState> stateLi;
  14. bool isLine;
  15. //绘制的内容区域
  16. late Rect _contentRect;
  17. double _contentPadding = 5.0;
  18. List<int> maDayList;
  19. final ChartStyle chartStyle;
  20. final ChartColors chartColors;
  21. final double mLineStrokeWidth = 1.0;
  22. double scaleX;
  23. late Paint mLinePaint;
  24. final VerticalTextAlignment verticalTextAlignment;
  25. MainRenderer(
  26. Rect mainRect,
  27. double maxValue,
  28. double minValue,
  29. double topPadding,
  30. this.stateLi,
  31. this.isLine,
  32. int fixedLength,
  33. this.chartStyle,
  34. this.chartColors,
  35. this.scaleX,
  36. this.verticalTextAlignment,
  37. [this.maDayList = const [5, 10, 20]])
  38. : super(
  39. chartRect: mainRect,
  40. maxValue: maxValue,
  41. minValue: minValue,
  42. topPadding: topPadding,
  43. fixedLength: fixedLength,
  44. gridColor: chartColors.gridColor) {
  45. mCandleWidth = this.chartStyle.candleWidth;
  46. mCandleLineWidth = this.chartStyle.candleLineWidth;
  47. mLinePaint = Paint()
  48. ..isAntiAlias = true
  49. ..style = PaintingStyle.stroke
  50. ..strokeWidth = mLineStrokeWidth
  51. ..color = this.chartColors.kLineColor;
  52. _contentRect = Rect.fromLTRB(
  53. chartRect.left,
  54. chartRect.top + _contentPadding,
  55. chartRect.right,
  56. chartRect.bottom - _contentPadding);
  57. if (maxValue == minValue) {
  58. maxValue *= 1.5;
  59. minValue /= 2;
  60. }
  61. scaleY = _contentRect.height / (maxValue - minValue);
  62. }
  63. @override
  64. void drawText(Canvas canvas, CandleEntity data, double x) {
  65. if (isLine == true) return;
  66. for (int i = 0; i < stateLi.length; ++i) {
  67. TextSpan? span;
  68. if (stateLi[i] == MainState.MA) {
  69. span = TextSpan(
  70. children: _createMATextSpan(data),
  71. );
  72. } else if (stateLi[i] == MainState.BOLL) {
  73. span = TextSpan(
  74. children: [
  75. if (data.up != 0)
  76. TextSpan(
  77. text: "BOLL:${format(data.mb)} ",
  78. style: getTextStyle(this.chartColors.ma5Color)),
  79. if (data.mb != 0)
  80. TextSpan(
  81. text: "UB:${format(data.up)} ",
  82. style: getTextStyle(this.chartColors.ma10Color)),
  83. if (data.dn != 0)
  84. TextSpan(
  85. text: "LB:${format(data.dn)} ",
  86. style: getTextStyle(this.chartColors.ma30Color)),
  87. ],
  88. );
  89. } else if (stateLi[i] == MainState.SAR) {
  90. span = TextSpan(
  91. text: "SAR:${format(data.sar)}",
  92. style: getTextStyle(this.chartColors.sarColor),
  93. );
  94. }
  95. if (span == null) return;
  96. TextPainter tp =
  97. TextPainter(text: span, textDirection: TextDirection.ltr);
  98. tp.layout();
  99. Offset offset = Offset(x, chartRect.top - topPadding + i * 12);
  100. canvas.drawRect(
  101. Rect.fromLTRB(
  102. offset.dx - 2,
  103. offset.dy - 2,
  104. tp.width + offset.dx + 2,
  105. tp.height + offset.dy + 2,
  106. ),
  107. Paint()..color = this.chartColors.bgColor);
  108. tp.paint(canvas, offset);
  109. }
  110. }
  111. List<InlineSpan> _createMATextSpan(CandleEntity data) {
  112. List<InlineSpan> result = [];
  113. for (int i = 0; i < (data.maValueList?.length ?? 0); i++) {
  114. if (data.maValueList?[i] != 0) {
  115. var item = TextSpan(
  116. text: "MA${maDayList[i]}:${format(data.maValueList![i])} ",
  117. style: getTextStyle(this.chartColors.getMAColor(i)));
  118. result.add(item);
  119. }
  120. }
  121. return result;
  122. }
  123. @override
  124. void drawChart(CandleEntity lastPoint, CandleEntity curPoint, double lastX,
  125. double curX, Size size, Canvas canvas) {
  126. if (isLine) {
  127. drawPolyline(lastPoint.close, curPoint.close, canvas, lastX, curX);
  128. } else {
  129. drawCandle(curPoint, canvas, curX);
  130. /// draw chart main state
  131. for (int i = 0; i < stateLi.length; ++i) {
  132. if (stateLi[i] == MainState.MA) {
  133. drawMaLine(lastPoint, curPoint, canvas, lastX, curX);
  134. } else if (stateLi[i] == MainState.BOLL) {
  135. drawBollLine(lastPoint, curPoint, canvas, lastX, curX);
  136. } else if (stateLi[i] == MainState.SAR) {
  137. drawSAR(lastPoint, curPoint, canvas, lastX, curX);
  138. }
  139. }
  140. }
  141. }
  142. Shader? mLineFillShader;
  143. Path? mLinePath, mLineFillPath;
  144. Paint mLineFillPaint = Paint()
  145. ..style = PaintingStyle.fill
  146. ..isAntiAlias = true;
  147. //画折线图
  148. drawPolyline(double lastPrice, double curPrice, Canvas canvas, double lastX,
  149. double curX) {
  150. // drawLine(lastPrice + 100, curPrice + 100, canvas, lastX, curX, ChartColors.kLineColor);
  151. mLinePath ??= Path();
  152. // if (lastX == curX) {
  153. // mLinePath.moveTo(lastX, getY(lastPrice));
  154. // } else {
  155. //// mLinePath.lineTo(curX, getY(curPrice));
  156. // mLinePath.cubicTo(
  157. // (lastX + curX) / 2, getY(lastPrice), (lastX + curX) / 2, getY(curPrice), curX, getY(curPrice));
  158. // }
  159. if (lastX == curX) lastX = 0; //起点位置填充
  160. mLinePath!.moveTo(lastX, getY(lastPrice));
  161. mLinePath!.cubicTo((lastX + curX) / 2, getY(lastPrice), (lastX + curX) / 2,
  162. getY(curPrice), curX, getY(curPrice));
  163. //画阴影
  164. mLineFillShader ??= LinearGradient(
  165. begin: Alignment.topCenter,
  166. end: Alignment.bottomCenter,
  167. tileMode: TileMode.clamp,
  168. colors: [
  169. this.chartColors.lineFillColor,
  170. this.chartColors.lineFillInsideColor
  171. ],
  172. ).createShader(Rect.fromLTRB(
  173. chartRect.left, chartRect.top, chartRect.right, chartRect.bottom));
  174. mLineFillPaint..shader = mLineFillShader;
  175. mLineFillPath ??= Path();
  176. mLineFillPath!.moveTo(lastX, chartRect.height + chartRect.top);
  177. mLineFillPath!.lineTo(lastX, getY(lastPrice));
  178. mLineFillPath!.cubicTo((lastX + curX) / 2, getY(lastPrice),
  179. (lastX + curX) / 2, getY(curPrice), curX, getY(curPrice));
  180. mLineFillPath!.lineTo(curX, chartRect.height + chartRect.top);
  181. mLineFillPath!.close();
  182. canvas.drawPath(mLineFillPath!, mLineFillPaint);
  183. mLineFillPath!.reset();
  184. canvas.drawPath(mLinePath!,
  185. mLinePaint..strokeWidth = (mLineStrokeWidth / scaleX).clamp(0.1, 1.0));
  186. mLinePath!.reset();
  187. }
  188. void drawMaLine(CandleEntity lastPoint, CandleEntity curPoint, Canvas canvas,
  189. double lastX, double curX) {
  190. for (int i = 0; i < (curPoint.maValueList?.length ?? 0); i++) {
  191. if (i == 3) {
  192. break;
  193. }
  194. if (lastPoint.maValueList?[i] != 0) {
  195. drawLine(lastPoint.maValueList?[i], curPoint.maValueList?[i], canvas,
  196. lastX, curX, this.chartColors.getMAColor(i));
  197. }
  198. }
  199. }
  200. void drawBollLine(CandleEntity lastPoint, CandleEntity curPoint,
  201. Canvas canvas, double lastX, double curX) {
  202. if (lastPoint.up != 0) {
  203. drawLine(lastPoint.up, curPoint.up, canvas, lastX, curX,
  204. this.chartColors.ma10Color);
  205. }
  206. if (lastPoint.mb != 0) {
  207. drawLine(lastPoint.mb, curPoint.mb, canvas, lastX, curX,
  208. this.chartColors.ma5Color);
  209. }
  210. if (lastPoint.dn != 0) {
  211. drawLine(lastPoint.dn, curPoint.dn, canvas, lastX, curX,
  212. this.chartColors.ma30Color);
  213. }
  214. }
  215. void drawSAR(CandleEntity lastPoint, CandleEntity curPoint, Canvas canvas,
  216. double lastX, double curX) {
  217. final sar = curPoint.sar;
  218. if (sar == null) return;
  219. final halfHL = (curPoint.high + curPoint.low) / 2;
  220. late final color;
  221. if (sar == halfHL) {
  222. color = this.chartColors.avgColor;
  223. } else if (sar < halfHL) {
  224. color = this.chartColors.upColor;
  225. } else {
  226. color = this.chartColors.dnColor;
  227. }
  228. drawCircle(canvas, curX, sar, color);
  229. }
  230. void drawCandle(CandleEntity curPoint, Canvas canvas, double curX) {
  231. var high = getY(curPoint.high);
  232. var low = getY(curPoint.low);
  233. var open = getY(curPoint.open);
  234. var close = getY(curPoint.close);
  235. double r = mCandleWidth / 2;
  236. double lineR = mCandleLineWidth / 2;
  237. if (open >= close) {
  238. // 实体高度>= CandleLineWidth
  239. if (open - close < mCandleLineWidth) {
  240. open = close + mCandleLineWidth;
  241. }
  242. chartPaint.color = this.chartColors.upColor;
  243. canvas.drawRect(
  244. Rect.fromLTRB(curX - r, close, curX + r, open), chartPaint);
  245. canvas.drawRect(
  246. Rect.fromLTRB(curX - lineR, high, curX + lineR, low), chartPaint);
  247. } else if (close > open) {
  248. // 实体高度>= CandleLineWidth
  249. if (close - open < mCandleLineWidth) {
  250. open = close - mCandleLineWidth;
  251. }
  252. chartPaint.color = this.chartColors.dnColor;
  253. canvas.drawRect(
  254. Rect.fromLTRB(curX - r, open, curX + r, close), chartPaint);
  255. canvas.drawRect(
  256. Rect.fromLTRB(curX - lineR, high, curX + lineR, low), chartPaint);
  257. }
  258. }
  259. @override
  260. void drawVerticalText(canvas, textStyle, int gridRows) {
  261. double rowSpace = chartRect.height / gridRows;
  262. for (var i = 0; i <= gridRows; ++i) {
  263. double value = (gridRows - i) * rowSpace / scaleY + minValue;
  264. TextSpan span = TextSpan(text: "${format(value)}", style: textStyle);
  265. TextPainter tp =
  266. TextPainter(text: span, textDirection: TextDirection.ltr);
  267. tp.layout();
  268. double offsetX;
  269. switch (verticalTextAlignment) {
  270. case VerticalTextAlignment.left:
  271. offsetX = 0;
  272. break;
  273. case VerticalTextAlignment.right:
  274. offsetX = chartRect.width - tp.width;
  275. break;
  276. }
  277. if (i == 0) {
  278. tp.paint(canvas, Offset(offsetX, topPadding));
  279. } else {
  280. tp.paint(
  281. canvas, Offset(offsetX, rowSpace * i - tp.height + topPadding));
  282. }
  283. }
  284. }
  285. @override
  286. void drawGrid(Canvas canvas, int gridRows, int gridColumns) {
  287. // final int gridRows = 4, gridColumns = 4;
  288. double rowSpace = chartRect.height / gridRows;
  289. for (int i = 0; i <= gridRows; i++) {
  290. canvas.drawLine(Offset(0, rowSpace * i + topPadding),
  291. Offset(chartRect.width, rowSpace * i + topPadding), gridPaint);
  292. }
  293. double columnSpace = chartRect.width / gridColumns;
  294. for (int i = 0; i <= columnSpace; i++) {
  295. canvas.drawLine(Offset(columnSpace * i, 0),
  296. Offset(columnSpace * i, chartRect.bottom), gridPaint);
  297. }
  298. }
  299. @override
  300. double getY(double y) {
  301. //For TrendLine
  302. updateTrendLineData();
  303. return (maxValue - y) * scaleY + _contentRect.top;
  304. }
  305. void updateTrendLineData() {
  306. trendLineMax = maxValue;
  307. trendLineScale = scaleY;
  308. trendLineContentRec = _contentRect.top;
  309. }
  310. }