setup.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. import { ref, watch } from "vue";
  2. import { getTheme, ThemeEnum } from '@/common/config/theme';
  3. import { deepMerge } from '@/utils/objHandle'
  4. import { EChartsOption } from 'echarts';
  5. import moment from 'moment';
  6. // 命名待优化
  7. type Colors = {
  8. backgroundColor: string, // 图表背景颜色
  9. axisPointerLabelColor: string,
  10. legendTextColor: string,
  11. xAxisLineColor: string
  12. yAxisLineColor: string,
  13. seriesMarkLabelColor: string,
  14. seriesMarkLineColor: string,
  15. upColor: string,
  16. downColor: string,
  17. equalColor: string,
  18. }
  19. export type Source = {
  20. date: string, // xAxis数据,必须是第一个属性
  21. open: number,
  22. close: number,
  23. lowest: number,
  24. highest: number,
  25. ma5: string,
  26. ma10: string,
  27. ma15: string,
  28. vol: number,
  29. macd: string,
  30. dif: string,
  31. dea: string,
  32. k: string,
  33. d: string,
  34. j: string,
  35. cci: string,
  36. }
  37. // 图表数据
  38. type ChartData = {
  39. source: Source[],
  40. }
  41. export function handleEchart() {
  42. const options = ref<EChartsOption>();
  43. // 当前主题
  44. const theme = getTheme();
  45. // 图表数据
  46. const chartData = ref<ChartData>({
  47. source: []
  48. });
  49. // 初始化图表配置
  50. const initOptions = (seriesType: string) => {
  51. const { source } = chartData.value;
  52. const option: EChartsOption = {
  53. dataset: {
  54. dimensions: ['date', 'open', 'close', 'lowest', 'highest', 'ma5', 'ma10', 'ma15', 'vol', 'macd', 'dif', 'dea', 'k', 'd', 'j', 'cci'],
  55. source
  56. },
  57. animation: false,
  58. axisPointer: {
  59. link: [
  60. {
  61. xAxisIndex: 'all'
  62. }
  63. ],
  64. },
  65. legend: {
  66. //图例控件,点击图例控制哪些系列不显示
  67. type: 'scroll',
  68. data: ['MA5', 'MA10', 'MA15'],
  69. selected: {
  70. VOL: seriesType === 'VOL',
  71. MACD: seriesType === 'MACD',
  72. DIF: seriesType === 'MACD',
  73. DEA: seriesType === 'MACD',
  74. K: seriesType === 'KDJ',
  75. D: seriesType === 'KDJ',
  76. J: seriesType === 'KDJ',
  77. CCI: seriesType === 'CCI',
  78. },
  79. itemWidth: 14,
  80. itemHeight: 2,
  81. left: '5%',
  82. top: 0,
  83. textStyle: {
  84. fontSize: 12,
  85. },
  86. },
  87. // 悬浮框
  88. tooltip: {
  89. trigger: 'axis',
  90. axisPointer: {
  91. type: 'cross',
  92. },
  93. backgroundColor: 'rgba(255,255,255,.95)',
  94. borderWidth: 1,
  95. borderRadius: 3,
  96. textStyle: {
  97. color: '#4d535c',
  98. },
  99. className: 'tooltip',
  100. formatter: (params: any) => {
  101. let result = '';
  102. params.forEach((item: any, index: number) => {
  103. if (index === 0) {
  104. result += '<div class="tooltip-title">' + moment(item.data.date).format('YYYY-MM-DD HH:mm') + '</div>';
  105. }
  106. if (item.seriesType === 'candlestick') {
  107. result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>开盘</span><span>' + item.data.open + '</span></div>';
  108. result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>收盘</span><span>' + item.data.close + '</span></div>';
  109. result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>最低</span><span>' + item.data.lowest + '</span></div>';
  110. result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>最高</span><span>' + item.data.highest + '</span></div>';
  111. } else {
  112. const key = item.dimensionNames[item.encode.y[0]];
  113. result += '<div class="tooltip-item"><span><i style="background-color:' + item.color + ';"></i>' + item.seriesName + '</span><span>' + item.data[key] + '</span></div>';
  114. }
  115. })
  116. return result;
  117. },
  118. },
  119. grid: [
  120. // K线
  121. {
  122. top: '8%',
  123. left: '8%',
  124. right: '8%',
  125. height: '55%',
  126. },
  127. // MACD、VOL、KDJ
  128. {
  129. top: '73%',
  130. left: '8%',
  131. right: '8%',
  132. height: '20%',
  133. }
  134. ],
  135. xAxis: [
  136. // K线时间轴
  137. {
  138. type: 'category',
  139. axisLabel: {
  140. formatter: (val: any) => moment(val).format('YYYY/MM/DD'),
  141. },
  142. splitLine: {
  143. show: true,
  144. },
  145. },
  146. // MACD、VOL、KDJ时间轴
  147. {
  148. type: 'category',
  149. gridIndex: 1,
  150. axisLabel: {
  151. show: false,
  152. formatter: (val: any) => moment(val).format('YYYY/MM/DD'),
  153. },
  154. axisLine: {
  155. show: false,
  156. },
  157. axisTick: {
  158. show: false,
  159. },
  160. splitLine: {
  161. show: true,
  162. },
  163. }
  164. ],
  165. yAxis: [
  166. {
  167. scale: true,
  168. },
  169. {
  170. scale: true,
  171. gridIndex: 1,
  172. }
  173. ],
  174. dataZoom: [
  175. {
  176. type: 'inside',
  177. xAxisIndex: [0, 1],
  178. startValue: source.length - 120, // 起始显示K线条数(最新120条)
  179. endValue: source.length,
  180. minValueSpan: 60, // 限制窗口缩放显示最少数据条数
  181. maxValueSpan: 400, // 限制窗口缩放显示最大数据条数
  182. },
  183. {
  184. show: false,
  185. type: 'slider',
  186. xAxisIndex: [0, 1],
  187. },
  188. ],
  189. series: [
  190. {
  191. name: 'K线',
  192. type: 'candlestick',
  193. // Y轴数据
  194. markLine: {
  195. animation: false,
  196. // 标线两端图标
  197. symbol: 'none',
  198. // 标线标签样式
  199. label: {
  200. fontWeight: 'bold',
  201. position: 'end',
  202. },
  203. // 标线样式
  204. lineStyle: {
  205. type: 'dashed',
  206. },
  207. data: [
  208. {
  209. // 当前价
  210. yAxis: source.length ? source[source.length - 1].close : 0,
  211. },
  212. ],
  213. },
  214. },
  215. {
  216. name: 'MA5',
  217. type: 'line',
  218. sampling: 'average', //折线图在数据量远大于像素点时候的降采样策略,开启后可以有效的优化图表的绘制效率
  219. smooth: true,
  220. symbol: 'none',
  221. lineStyle: {
  222. width: 1,
  223. opacity: 0.8,
  224. },
  225. },
  226. {
  227. name: 'MA10',
  228. type: 'line',
  229. sampling: 'average',
  230. smooth: true,
  231. symbol: 'none',
  232. lineStyle: {
  233. width: 1,
  234. opacity: 0.8,
  235. },
  236. },
  237. {
  238. name: 'MA15',
  239. type: 'line',
  240. sampling: 'average',
  241. smooth: true,
  242. symbol: 'none',
  243. lineStyle: {
  244. width: 1,
  245. opacity: 0.8,
  246. },
  247. },
  248. {
  249. name: 'VOL',
  250. type: 'bar',
  251. sampling: 'average',
  252. xAxisIndex: 1,
  253. yAxisIndex: 1,
  254. barWidth: '60%',
  255. },
  256. {
  257. name: 'MACD',
  258. type: 'bar',
  259. sampling: 'average',
  260. xAxisIndex: 1,
  261. yAxisIndex: 1,
  262. barWidth: '20%',
  263. },
  264. {
  265. name: 'DIF',
  266. type: 'line',
  267. sampling: 'average',
  268. xAxisIndex: 1,
  269. yAxisIndex: 1,
  270. smooth: true,
  271. symbol: 'none',
  272. lineStyle: {
  273. width: 1,
  274. opacity: 0.8,
  275. },
  276. },
  277. {
  278. name: 'DEA',
  279. type: 'line',
  280. sampling: 'average',
  281. xAxisIndex: 1,
  282. yAxisIndex: 1,
  283. smooth: true,
  284. symbol: 'none',
  285. lineStyle: {
  286. width: 1,
  287. opacity: 0.8,
  288. },
  289. },
  290. {
  291. name: 'K',
  292. type: 'line',
  293. sampling: 'average',
  294. xAxisIndex: 1,
  295. yAxisIndex: 1,
  296. smooth: true,
  297. symbol: 'none',
  298. lineStyle: {
  299. width: 1,
  300. opacity: 0.8,
  301. },
  302. },
  303. {
  304. name: 'D',
  305. type: 'line',
  306. sampling: 'average',
  307. xAxisIndex: 1,
  308. yAxisIndex: 1,
  309. smooth: true,
  310. symbol: 'none',
  311. lineStyle: {
  312. width: 1,
  313. opacity: 0.8,
  314. },
  315. },
  316. {
  317. name: 'J',
  318. type: 'line',
  319. sampling: 'average',
  320. xAxisIndex: 1,
  321. yAxisIndex: 1,
  322. smooth: true,
  323. symbol: 'none',
  324. lineStyle: {
  325. width: 1,
  326. opacity: 0.8,
  327. },
  328. },
  329. {
  330. name: 'CCI',
  331. type: 'line',
  332. sampling: 'average',
  333. xAxisIndex: 1,
  334. yAxisIndex: 1,
  335. smooth: true,
  336. symbol: 'none',
  337. lineStyle: {
  338. width: 1,
  339. opacity: 0.8,
  340. },
  341. },
  342. ],
  343. };
  344. options.value = deepMerge(option, getColors(theme.value));
  345. };
  346. // 动态更新数据
  347. const updateOptions = (seriesType: string) => {
  348. const { source } = chartData.value;
  349. if (source.length) {
  350. options.value = {
  351. legend: {
  352. selected: {
  353. VOL: seriesType === 'VOL',
  354. MACD: seriesType === 'MACD',
  355. DIF: seriesType === 'MACD',
  356. DEA: seriesType === 'MACD',
  357. K: seriesType === 'KDJ',
  358. D: seriesType === 'KDJ',
  359. J: seriesType === 'KDJ',
  360. CCI: seriesType === 'CCI',
  361. },
  362. },
  363. dataset: {
  364. source
  365. },
  366. series: [
  367. {
  368. name: 'K线',
  369. markLine: {
  370. data: [
  371. {
  372. yAxis: source[source.length - 1].close,
  373. },
  374. ],
  375. },
  376. },
  377. ],
  378. }
  379. }
  380. };
  381. // 设置图表样式
  382. const setColors = (colors: Colors): EChartsOption => {
  383. return {
  384. // 图表背景颜色
  385. backgroundColor: colors.backgroundColor,
  386. axisPointer: {
  387. label: {
  388. color: colors.axisPointerLabelColor,
  389. },
  390. },
  391. legend: {
  392. textStyle: {
  393. color: colors.legendTextColor,
  394. },
  395. },
  396. xAxis: [
  397. {
  398. splitLine: {
  399. lineStyle: {
  400. // 坐标分隔线颜色
  401. color: colors.xAxisLineColor,
  402. },
  403. },
  404. },
  405. {
  406. splitLine: {
  407. lineStyle: {
  408. // 坐标分隔线颜色
  409. color: colors.xAxisLineColor,
  410. },
  411. },
  412. }
  413. ],
  414. yAxis: [
  415. {
  416. splitLine: {
  417. lineStyle: {
  418. // 坐标分隔线颜色
  419. color: colors.xAxisLineColor,
  420. },
  421. },
  422. },
  423. {
  424. splitLine: {
  425. lineStyle: {
  426. // 坐标分隔线颜色
  427. color: colors.xAxisLineColor,
  428. },
  429. },
  430. }
  431. ],
  432. series: [
  433. {
  434. name: 'K线',
  435. markLine: {
  436. // 标线标签样式
  437. label: {
  438. color: colors.seriesMarkLabelColor,
  439. },
  440. // 标线样式
  441. lineStyle: {
  442. color: colors.seriesMarkLineColor,
  443. },
  444. },
  445. },
  446. {
  447. name: 'MA5',
  448. },
  449. {
  450. name: 'MA10',
  451. },
  452. {
  453. name: 'MA15',
  454. },
  455. {
  456. name: 'VOL',
  457. itemStyle: {
  458. color: (params: any) => {
  459. // 判断收盘价是否高于或等于开盘价
  460. if (params.data.close >= params.data.open) {
  461. return colors.upColor;
  462. } else {
  463. return colors.downColor;
  464. }
  465. },
  466. }
  467. },
  468. {
  469. name: 'MACD',
  470. itemStyle: {
  471. color: (params: any) => {
  472. if (params.data.macd > 0) {
  473. return colors.upColor;
  474. } else {
  475. return colors.downColor;
  476. }
  477. },
  478. }
  479. },
  480. ]
  481. }
  482. }
  483. // 获取图表样式配置
  484. const getColors = (theme: ThemeEnum) => {
  485. switch (theme) {
  486. case ThemeEnum.default:
  487. case ThemeEnum.dark:
  488. return setColors({
  489. backgroundColor: 'transparent',
  490. axisPointerLabelColor: '#fff',
  491. legendTextColor: '#0e99e2',
  492. xAxisLineColor: '#171B1D',
  493. yAxisLineColor: '#171B1D',
  494. seriesMarkLabelColor: '#3C454B',
  495. seriesMarkLineColor: '#33393D',
  496. upColor: '#eb5454',
  497. downColor: '#47b262',
  498. equalColor: '#fff',
  499. });
  500. case ThemeEnum.light:
  501. return setColors({
  502. backgroundColor: 'transparent',
  503. axisPointerLabelColor: '#fff',
  504. legendTextColor: '#FC9618',
  505. xAxisLineColor: '#DAE5EC',
  506. yAxisLineColor: '#DAE5EC',
  507. seriesMarkLabelColor: '#ACB8C0',
  508. seriesMarkLineColor: '#ACB8C0',
  509. upColor: '#eb5454',
  510. downColor: '#47b262',
  511. equalColor: '#333',
  512. });
  513. }
  514. }
  515. watch(theme, (val) => {
  516. options.value = getColors(val);
  517. });
  518. return {
  519. chartData,
  520. options,
  521. initOptions,
  522. updateOptions,
  523. }
  524. }