base_chart_painter.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import 'dart:math';
  2. import 'package:flutter/material.dart'
  3. show Color, TextStyle, Rect, Canvas, Size, CustomPainter;
  4. import 'package:k_chart_plus/utils/date_format_util.dart';
  5. import '../chart_style.dart' show ChartStyle;
  6. import '../entity/k_line_entity.dart';
  7. import '../k_chart_widget.dart';
  8. import 'base_dimension.dart';
  9. export 'package:flutter/material.dart'
  10. show Color, required, TextStyle, Rect, Canvas, Size, CustomPainter;
  11. /// BaseChartPainter
  12. abstract class BaseChartPainter extends CustomPainter {
  13. static double maxScrollX = 0.0;
  14. List<KLineEntity>? datas; // data of chart
  15. Set<MainState> mainStateLi; //MainState mainState;
  16. Set<SecondaryState> secondaryStateLi;
  17. bool volHidden;
  18. bool isTapShowInfoDialog;
  19. double scaleX = 1.0, scrollX = 0.0, selectX;
  20. bool isLongPress = false;
  21. bool isOnTap;
  22. bool isLine;
  23. late Rect mMainLabelRect;
  24. /// Rectangle box of main chart
  25. late Rect mMainRect;
  26. /// Rectangle box of the vol chart
  27. Rect? mVolRect;
  28. /// Secondary list support
  29. List<RenderRect> mSecondaryRectList = [];
  30. late double mDisplayHeight, mWidth;
  31. // padding
  32. double mTopPadding = 20.0, mBottomPadding = 20.0, mChildPadding = 12.0;
  33. // grid: rows - columns
  34. int mGridRows = 4, mGridColumns = 4;
  35. int mStartIndex = 0, mStopIndex = 0;
  36. double mMainMaxValue = double.minPositive, mMainMinValue = double.maxFinite;
  37. double mVolMaxValue = double.minPositive, mVolMinValue = double.maxFinite;
  38. double mTranslateX = double.minPositive;
  39. int mMainMaxIndex = 0, mMainMinIndex = 0;
  40. double mMainHighMaxValue = double.minPositive,
  41. mMainLowMinValue = double.maxFinite;
  42. int mItemCount = 0;
  43. double mDataLen = 0.0; // the data occupies the total length of the screen
  44. final ChartStyle chartStyle;
  45. late double mPointWidth;
  46. // format time
  47. List<String> mFormats = [yyyy, '-', mm, '-', dd, ' ', HH, ':', nn];
  48. double xFrontPadding;
  49. /// base dimension
  50. final BaseDimension baseDimension;
  51. /// constructor BaseChartPainter
  52. ///
  53. BaseChartPainter(
  54. this.chartStyle, {
  55. this.datas,
  56. required this.scaleX,
  57. required this.scrollX,
  58. required this.isLongPress,
  59. required this.selectX,
  60. required this.xFrontPadding,
  61. required this.baseDimension,
  62. this.isOnTap = false,
  63. this.mainStateLi = const <MainState>{},
  64. this.volHidden = false,
  65. this.isTapShowInfoDialog = false,
  66. this.secondaryStateLi = const <SecondaryState>{},
  67. this.isLine = false,
  68. }) {
  69. mItemCount = datas?.length ?? 0;
  70. mPointWidth = this.chartStyle.pointWidth;
  71. mTopPadding = this.chartStyle.topPadding +
  72. baseDimension.totalLabelHeight; // space to display text of main chart
  73. mBottomPadding = this.chartStyle.bottomPadding;
  74. mChildPadding = this.chartStyle.childPadding;
  75. mGridRows = this.chartStyle.gridRows;
  76. mGridColumns = this.chartStyle.gridColumns;
  77. mDataLen = mItemCount * mPointWidth;
  78. initFormats();
  79. }
  80. /// init format time
  81. void initFormats() {
  82. if (this.chartStyle.dateTimeFormat != null) {
  83. mFormats = this.chartStyle.dateTimeFormat!;
  84. return;
  85. }
  86. if (mItemCount < 2) {
  87. mFormats = [yyyy, '-', mm, '-', dd, ' ', HH, ':', nn];
  88. return;
  89. }
  90. int firstTime = datas!.first.time ?? 0;
  91. int secondTime = datas![1].time ?? 0;
  92. int time = secondTime - firstTime;
  93. time ~/= 1000;
  94. // monthly line
  95. if (time >= 24 * 60 * 60 * 28) {
  96. mFormats = [yy, '-', mm];
  97. } else if (time >= 24 * 60 * 60) {
  98. // daily line
  99. mFormats = [yy, '-', mm, '-', dd];
  100. } else {
  101. // hour line
  102. mFormats = [mm, '-', dd, ' ', HH, ':', nn];
  103. }
  104. }
  105. /// paint chart
  106. @override
  107. void paint(Canvas canvas, Size size) {
  108. canvas.clipRect(Rect.fromLTRB(0, 0, size.width, size.height));
  109. mDisplayHeight = size.height - mTopPadding - mBottomPadding;
  110. mWidth = size.width;
  111. initRect(size);
  112. calculateValue();
  113. initChartRenderer();
  114. canvas.save();
  115. canvas.scale(1, 1);
  116. drawBg(canvas, size);
  117. drawGrid(canvas);
  118. if (datas != null && datas!.isNotEmpty) {
  119. drawChart(canvas, size);
  120. drawVerticalText(canvas);
  121. drawDate(canvas, size);
  122. drawText(canvas, datas!.last, 5);
  123. drawMaxAndMin(canvas);
  124. drawNowPrice(canvas);
  125. if (isLongPress == true || (isTapShowInfoDialog && isOnTap)) {
  126. drawCrossLineText(canvas, size);
  127. }
  128. }
  129. canvas.restore();
  130. }
  131. /// init chart renderer
  132. void initChartRenderer();
  133. /// draw the background of chart
  134. void drawBg(Canvas canvas, Size size);
  135. /// draw the grid of chart
  136. void drawGrid(canvas);
  137. /// draw chart
  138. void drawChart(Canvas canvas, Size size);
  139. /// draw vertical text
  140. void drawVerticalText(canvas);
  141. /// draw date
  142. void drawDate(Canvas canvas, Size size);
  143. /// draw text
  144. void drawText(Canvas canvas, KLineEntity data, double x);
  145. /// draw maximum and minimum values
  146. void drawMaxAndMin(Canvas canvas);
  147. /// draw the current price
  148. void drawNowPrice(Canvas canvas);
  149. /// draw cross line
  150. void drawCrossLine(Canvas canvas, Size size);
  151. /// draw text of the cross line
  152. void drawCrossLineText(Canvas canvas, Size size);
  153. /// init the rectangle box to draw chart
  154. void initRect(Size size) {
  155. double volHeight = baseDimension.mVolumeHeight;
  156. double secondaryHeight = baseDimension.mSecondaryHeight;
  157. double mainHeight = mDisplayHeight;
  158. mainHeight -= volHeight;
  159. mainHeight -= baseDimension.totalSecondaryHeight;
  160. mMainRect = Rect.fromLTRB(0, mTopPadding, mWidth, mTopPadding + mainHeight);
  161. if (volHidden != true) {
  162. mVolRect = Rect.fromLTRB(0, mMainRect.bottom + mChildPadding, mWidth,
  163. mMainRect.bottom + volHeight);
  164. }
  165. mSecondaryRectList.clear();
  166. for (int i = 0; i < secondaryStateLi.length; ++i) {
  167. mSecondaryRectList.add(RenderRect(
  168. Rect.fromLTRB(
  169. 0,
  170. mMainRect.bottom + volHeight + i * secondaryHeight + mChildPadding,
  171. mWidth,
  172. mMainRect.bottom +
  173. volHeight +
  174. i * secondaryHeight +
  175. secondaryHeight),
  176. ));
  177. }
  178. }
  179. /// calculate values
  180. calculateValue() {
  181. if (datas == null) return;
  182. if (datas!.isEmpty) return;
  183. maxScrollX = getMinTranslateX().abs();
  184. setTranslateXFromScrollX(scrollX);
  185. mStartIndex = indexOfTranslateX(xToTranslateX(0));
  186. mStopIndex = indexOfTranslateX(xToTranslateX(mWidth));
  187. for (int i = mStartIndex; i <= mStopIndex; i++) {
  188. var item = datas![i];
  189. getMainMaxMinValue(item, i);
  190. getVolMaxMinValue(item);
  191. for (int idx = 0; idx < mSecondaryRectList.length; ++idx) {
  192. getSecondaryMaxMinValue(idx, item);
  193. }
  194. }
  195. }
  196. /// compute maximum and minimum value
  197. void getMainMaxMinValue(KLineEntity item, int i) {
  198. double maxPrice = item.high;
  199. double minPrice = item.low;
  200. for (int i = 0; i < mainStateLi.length; ++i) {
  201. if (mainStateLi.elementAt(i) == MainState.MA) {
  202. maxPrice = max(maxPrice, _findMaxMA(item.maValueList ?? [0]));
  203. minPrice = min(minPrice, _findMinMA(item.maValueList ?? [0]));
  204. } else if (mainStateLi.elementAt(i) == MainState.BOLL) {
  205. maxPrice = max(maxPrice, item.up ?? 0);
  206. minPrice = min(minPrice, item.dn ?? 0);
  207. } else if (mainStateLi.elementAt(i) == MainState.SAR) {
  208. maxPrice = max(maxPrice, item.sar ?? 0);
  209. minPrice = min(minPrice, item.sar ?? 0);
  210. }
  211. }
  212. mMainMaxValue = max(mMainMaxValue, maxPrice);
  213. mMainMinValue = min(mMainMinValue, minPrice);
  214. if (mMainHighMaxValue < item.high) {
  215. mMainHighMaxValue = item.high;
  216. mMainMaxIndex = i;
  217. }
  218. if (mMainLowMinValue > item.low) {
  219. mMainLowMinValue = item.low;
  220. mMainMinIndex = i;
  221. }
  222. if (isLine == true) {
  223. mMainMaxValue = max(mMainMaxValue, item.close);
  224. mMainMinValue = min(mMainMinValue, item.close);
  225. }
  226. }
  227. // find maximum of the MA
  228. double _findMaxMA(List<double> a) {
  229. double result = double.minPositive;
  230. for (double i in a) {
  231. result = max(result, i);
  232. }
  233. return result;
  234. }
  235. // find minimum of the MA
  236. double _findMinMA(List<double> a) {
  237. double result = double.maxFinite;
  238. for (double i in a) {
  239. result = min(result, i == 0 ? double.maxFinite : i);
  240. }
  241. return result;
  242. }
  243. // get the maximum and minimum of the Vol value
  244. void getVolMaxMinValue(KLineEntity item) {
  245. mVolMaxValue = max(mVolMaxValue,
  246. max(item.vol, max(item.MA5Volume ?? 0, item.MA10Volume ?? 0)));
  247. mVolMinValue = min(mVolMinValue,
  248. min(item.vol, min(item.MA5Volume ?? 0, item.MA10Volume ?? 0)));
  249. }
  250. // compute maximum and minimum of secondary value
  251. getSecondaryMaxMinValue(int index, KLineEntity item) {
  252. SecondaryState secondaryState = secondaryStateLi.elementAt(index);
  253. switch (secondaryState) {
  254. // MACD
  255. case SecondaryState.MACD:
  256. if (item.macd != null) {
  257. mSecondaryRectList[index].mMaxValue = max(
  258. mSecondaryRectList[index].mMaxValue,
  259. max(item.macd!, max(item.dif!, item.dea!)));
  260. mSecondaryRectList[index].mMinValue = min(
  261. mSecondaryRectList[index].mMinValue,
  262. min(item.macd!, min(item.dif!, item.dea!)));
  263. }
  264. break;
  265. // KDJ
  266. case SecondaryState.KDJ:
  267. if (item.d != null) {
  268. mSecondaryRectList[index].mMaxValue = max(
  269. mSecondaryRectList[index].mMaxValue,
  270. max(item.k!, max(item.d!, item.j!)));
  271. mSecondaryRectList[index].mMinValue = min(
  272. mSecondaryRectList[index].mMinValue,
  273. min(item.k!, min(item.d!, item.j!)));
  274. }
  275. break;
  276. // RSI
  277. case SecondaryState.RSI:
  278. if (item.rsi != null) {
  279. mSecondaryRectList[index].mMaxValue =
  280. max(mSecondaryRectList[index].mMaxValue, item.rsi!);
  281. mSecondaryRectList[index].mMinValue =
  282. min(mSecondaryRectList[index].mMinValue, item.rsi!);
  283. }
  284. break;
  285. // WR
  286. case SecondaryState.WR:
  287. mSecondaryRectList[index].mMaxValue = 0;
  288. mSecondaryRectList[index].mMinValue = -100;
  289. break;
  290. // CCI
  291. case SecondaryState.CCI:
  292. if (item.cci != null) {
  293. mSecondaryRectList[index].mMaxValue =
  294. max(mSecondaryRectList[index].mMaxValue, item.cci!);
  295. mSecondaryRectList[index].mMinValue =
  296. min(mSecondaryRectList[index].mMinValue, item.cci!);
  297. }
  298. break;
  299. // default:
  300. // mSecondaryRectList[index].mMaxValue = 0;
  301. // mSecondaryRectList[index].mMinValue = 0;
  302. // break;
  303. }
  304. }
  305. // translate x
  306. double xToTranslateX(double x) => -mTranslateX + x / scaleX;
  307. int indexOfTranslateX(double translateX) =>
  308. _indexOfTranslateX(translateX, 0, mItemCount - 1);
  309. /// Using binary search for the index of the current value
  310. int _indexOfTranslateX(double translateX, int start, int end) {
  311. if (end == start || end == -1) {
  312. return start;
  313. }
  314. if (end - start == 1) {
  315. double startValue = getX(start);
  316. double endValue = getX(end);
  317. return (translateX - startValue).abs() < (translateX - endValue).abs()
  318. ? start
  319. : end;
  320. }
  321. int mid = start + (end - start) ~/ 2;
  322. double midValue = getX(mid);
  323. if (translateX < midValue) {
  324. return _indexOfTranslateX(translateX, start, mid);
  325. } else if (translateX > midValue) {
  326. return _indexOfTranslateX(translateX, mid, end);
  327. } else {
  328. return mid;
  329. }
  330. }
  331. /// Get x coordinate based on index
  332. /// + mPointWidth / 2 to prevent the first and last K-line from displaying incorrectly
  333. /// @param position index value
  334. double getX(int position) => position * mPointWidth + mPointWidth / 2;
  335. KLineEntity getItem(int position) {
  336. return datas![position];
  337. // if (datas != null) {
  338. // return datas[position];
  339. // } else {
  340. // return null;
  341. // }
  342. }
  343. /// scrollX convert to TranslateX
  344. void setTranslateXFromScrollX(double scrollX) =>
  345. mTranslateX = scrollX + getMinTranslateX();
  346. /// get the minimum value of translation
  347. double getMinTranslateX() {
  348. var x = -mDataLen + mWidth / scaleX - mPointWidth / 2 - xFrontPadding;
  349. return x >= 0 ? 0.0 : x;
  350. }
  351. /// calculate the value of x after long pressing and convert to [index]
  352. int calculateSelectedX(double selectX) {
  353. int mSelectedIndex = indexOfTranslateX(xToTranslateX(selectX));
  354. if (mSelectedIndex < mStartIndex) {
  355. mSelectedIndex = mStartIndex;
  356. }
  357. if (mSelectedIndex > mStopIndex) {
  358. mSelectedIndex = mStopIndex;
  359. }
  360. return mSelectedIndex;
  361. }
  362. /// translateX is converted to X in view
  363. double translateXtoX(double translateX) =>
  364. (translateX + mTranslateX) * scaleX;
  365. /// define text style
  366. TextStyle getTextStyle(Color color) {
  367. return TextStyle(fontSize: 10.0, color: color);
  368. }
  369. @override
  370. bool shouldRepaint(BaseChartPainter oldDelegate) {
  371. return true;
  372. }
  373. }
  374. /// Render Rectangle
  375. class RenderRect {
  376. Rect mRect;
  377. double mMaxValue = double.minPositive, mMinValue = double.maxFinite;
  378. RenderRect(this.mRect);
  379. }