You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

661 lines
17 KiB

4 months ago
4 months ago
4 months ago
  1. <template>
  2. <!-- <div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div> -->
  3. <div class="qxjmqbox">
  4. <div ref="KlineCanvs" class="qxjmqEcharts"></div>
  5. </div>
  6. </template>
  7. <script setup>
  8. import { ref, onMounted, onBeforeUnmount, toRef } from "vue";
  9. import * as echarts from "echarts";
  10. import { color } from "echarts/lib/export";
  11. defineExpose({ initQXNLZHEcharts });
  12. const KlineCanvs = ref(null);
  13. let KlineCanvsChart = null;
  14. const heatmapData = ref([]); // 存储热力图数据
  15. // 假数据
  16. const rawData2 = ref([]);
  17. const lineData3 = ref([]);
  18. function initQXNLZHEcharts(kline, qxnlzhqData) {
  19. // qxnlzhqData[9][9] = 1;
  20. // qxnlzhqData[2][9] = 1;
  21. rawData2.value = qxnlzhqData;
  22. // 处理热力图数据
  23. const processedHeatmap = [];
  24. rawData2.value.forEach((item, dateIndex) => {
  25. const [date, d1, d2, d3, d4, d5, d6, d7, d8, d9] = item;
  26. processedHeatmap.push([
  27. dateIndex,
  28. 0,
  29. d4,
  30. d8 == 1 ? "green" : "transparent",
  31. ]); // 数据1
  32. processedHeatmap.push([
  33. dateIndex,
  34. 1,
  35. d3,
  36. d7 == 1 ? "purple" : "transparent",
  37. ]); // 数据2
  38. processedHeatmap.push([dateIndex, 2, d2, d6 == 1 ? "red" : "transparent"]); // 数据3
  39. processedHeatmap.push([
  40. dateIndex,
  41. 3,
  42. d1,
  43. d5 == 1 ? "yellow" : "transparent",
  44. ]); // 数据4
  45. });
  46. heatmapData.value = processedHeatmap;
  47. // 处理折线图数据
  48. lineData3.value = rawData2.value.map((item) => item[9]);
  49. console.log("热力图数据:", heatmapData.value);
  50. console.log("折线图数据:", lineData3.value);
  51. const data = kline;
  52. // 切割数据方法
  53. const spliteDate = (a) => {
  54. const categoryData = [];
  55. let value = [];
  56. for (let i = 0; i < a.length; i++) {
  57. // 使用非破坏性的方法,避免修改原数组
  58. categoryData.push(a[i][0]);
  59. value.push(a[i].slice(1)); // 使用 slice 而不是 splice
  60. }
  61. return { categoryData, value };
  62. };
  63. let normalHeatmap = []
  64. let rotateHeatmap = []
  65. processedHeatmap.forEach((item) => {
  66. if (item[2] > 999) {
  67. rotateHeatmap.push(item)
  68. } else {
  69. normalHeatmap.push(item)
  70. }
  71. })
  72. const dealData = spliteDate(data);
  73. // 给配置项
  74. const KlineOption = {
  75. tooltip: {
  76. trigger: 'axis', // 触发类型 坐标轴触发
  77. confine: true, // 限制tooltip不超出容器
  78. axisPointer: {
  79. type: 'cross', // 十字准星效果
  80. lineStyle: {
  81. color: 'grey',
  82. width: 1,
  83. type: 'dashed'
  84. },
  85. crossStyle: {
  86. color: '#999'
  87. },
  88. label: {
  89. backgroundColor: 'rgba(0, 0, 0, 0.8)',
  90. color: '#fff',
  91. borderColor: '#fff',
  92. borderWidth: 1
  93. }
  94. },
  95. backgroundColor: 'rgba(232, 232, 242, 0.87)',
  96. borderColor: '#fff',
  97. borderWidth: 1,
  98. borderRadius: 8,
  99. padding: 10,
  100. textStyle: {
  101. color: 'black',
  102. fontSize: 12
  103. },
  104. formatter: function (params) {
  105. if (!params || params.length === 0) return ''
  106. // 找到K线数据
  107. const klineParam = params.find(param => param.seriesType === 'candlestick')
  108. if (!klineParam) return ''
  109. const date = klineParam.name
  110. // 开收低高分别取参数的第2到第5个数
  111. const open = klineParam.data[1]
  112. const close = klineParam.data[2]
  113. const low = klineParam.data[3]
  114. const high = klineParam.data[4]
  115. return `<span style="color:black;font-weight:bold;">日期: ${date}</span><br/><span style="color:black;">开盘价: ${open}</span><br/><span style="color:black;">收盘价: ${close}</span><br/><span style="color:black;">最低价: ${low}</span><br/><span style="color:black;">最高价: ${high}</span>`
  116. }
  117. },
  118. //控制坐标轴
  119. grid: [
  120. {
  121. top: "5%",
  122. height: window.innerWidth <= 768 ? "35%" : "40%",
  123. width: window.innerWidth <= 768 ? "70%" : "80%",
  124. left: window.outerWidth <= 768 ? "20%" : "10%",
  125. },
  126. {
  127. top: window.innerWidth <= 768 ? "40%" : "45%",
  128. height: "35%",
  129. width: window.innerWidth <= 768 ? "70%" : "80%",
  130. left: window.outerWidth <= 768 ? "20%" : "10%",
  131. },
  132. {
  133. top: window.innerWidth <= 768 ? "75%" : "80%",
  134. height: "2%",
  135. width: window.innerWidth <= 768 ? "70%" : "80%",
  136. left: window.outerWidth <= 768 ? "20%" : "10%",
  137. },
  138. ],
  139. visualMap: [
  140. {
  141. show: false, // 不显示控制条
  142. seriesIndex: 1, // 作用于热力图的系列
  143. min: 0,
  144. max: 2000, // 根据您的数据范围调整
  145. calculable: true,
  146. orient: "horizontal",
  147. left: "center",
  148. bottom: "15%",
  149. inRange: {
  150. color: ["transparent"],
  151. },
  152. },
  153. ],
  154. // 横坐标内容
  155. xAxis: [
  156. {
  157. type: "category",
  158. gridIndex: 0,
  159. data: dealData.categoryData,
  160. axisPointer: {
  161. show: true,
  162. type: "line",
  163. label: {
  164. show: true,
  165. backgroundColor: 'rgba(0,191,255)',
  166. color: 'black'
  167. },
  168. },
  169. axisTick: { show: false }, // 隐藏刻度线
  170. axisLabel: { show: false, rotate: 45 }, // 隐藏刻度标签
  171. axisLine: {
  172. show: true,
  173. lineStyle: {
  174. color: "white",
  175. },
  176. },
  177. },
  178. {
  179. type: "category",
  180. gridIndex: 1,
  181. data: dealData.categoryData,
  182. axisTick: { show: false }, // 隐藏刻度线
  183. axisLabel: { show: false, rotate: 45 }, // 隐藏刻度标签
  184. splitLine: {
  185. show: true,
  186. lineStyle: {
  187. color: "white",
  188. // 间隔线类型实线
  189. type: "solid",
  190. },
  191. interval: 0,
  192. },
  193. },
  194. {
  195. type: "category",
  196. gridIndex: 2,
  197. data: dealData.categoryData,
  198. axisLine: { lineStyle: { color: "white" } },
  199. axisPointer: {
  200. show: false,
  201. label: {
  202. show: false,
  203. },
  204. type: "line",
  205. },
  206. axisTick: {
  207. show: true,
  208. alignWithLabel: true, // 刻度线与标签对齐
  209. lineStyle: {
  210. color: "white", // 与十字线颜色保持一致
  211. width: 1,
  212. type: "dashed" // 与十字线样式保持一致
  213. }
  214. },
  215. },
  216. ],
  217. yAxis: [
  218. {
  219. scale: true,
  220. axisLabel: {
  221. formatter: function (value) {
  222. return value;
  223. },
  224. },
  225. axisLine: {
  226. show: true,
  227. lineStyle: { color: "white" },
  228. },
  229. splitLine: {
  230. show: false,
  231. },
  232. axisPointer: {
  233. show: true,
  234. label: {
  235. show: true,
  236. backgroundColor: 'rgba(0,255,127)',
  237. color: 'black'
  238. },
  239. type: "line",
  240. },
  241. },
  242. {
  243. gridIndex: 1,
  244. type: "category",
  245. data: [0, 1, 2, 3], // 倒序显示
  246. axisLine: { lineStyle: { color: "white" } },
  247. axisLabel: {
  248. show: false, // 显示刻度标签
  249. color: "#fff", // 白色文字
  250. backgroundColor: "transparent",
  251. fontSize: 12,
  252. margin: 8,
  253. },
  254. axisPointer: {
  255. show: true,
  256. label: {
  257. show: false,
  258. },
  259. type: "line",
  260. },
  261. axisTick: { show: false }, // 隐藏刻度线
  262. splitLine: {
  263. show: true,
  264. lineStyle: {
  265. color: "#8392A5",
  266. width: 1,
  267. type: "solid",
  268. },
  269. interval: 0, // 强制显示间隔
  270. },
  271. },
  272. {
  273. gridIndex: 2,
  274. type: "value",
  275. axisLine: {
  276. show: true,
  277. lineStyle: { color: "white" },
  278. },
  279. splitLine: {
  280. show: !1,
  281. },
  282. axisTick: { show: false }, // 隐藏刻度线
  283. axisLabel: { show: false }, // 隐藏刻度标签
  284. axisPointer: {
  285. show: false,
  286. label: {
  287. show: false,
  288. },
  289. type: "line",
  290. },
  291. },
  292. ],
  293. // 下拉条
  294. dataZoom: [
  295. {
  296. textStyle: {
  297. color: "white", // 字体颜色
  298. },
  299. // handleIcon:
  300. // "M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z",
  301. // handleSize: "80%",
  302. dataBackground: {
  303. areaStyle: {
  304. color: "#8392A5",
  305. },
  306. lineStyle: {
  307. opacity: 0.8,
  308. color: "#8392A5",
  309. },
  310. },
  311. xAxisIndex: [0, 1, 2],
  312. // handleStyle: {
  313. // color: "#fff",
  314. // shadowBlur: 3,
  315. // shadowColor: "rgba(0, 0, 0, 0.6)",
  316. // shadowOffsetX: 2,
  317. // shadowOffsetY: 2,
  318. // },
  319. start: 50, // 默认展示后半部分数据
  320. end: 100,
  321. bottom: window.innerWidth <= 768 ? "5%" : "7%", // 下移数据缩放滑块
  322. },
  323. // {
  324. // show: true,
  325. // type: "slider",
  326. // xAxisIndex: [0, 1, 2],
  327. // bottom: "0%",
  328. // textStyle: {
  329. // color: "white",
  330. // },
  331. // },
  332. {
  333. type: "inside",
  334. xAxisIndex: [0, 1, 2],
  335. filterMode: "filter",
  336. },
  337. ],
  338. series: [
  339. {
  340. type: "candlestick",
  341. name: "\u65e5K",
  342. // 数据
  343. data: dealData.value,
  344. itemStyle: {
  345. normal: {
  346. color0: "red", // 阴线颜色
  347. color: "#0CF49B", // 阳线颜色
  348. borderColor0: "#FD1050", // 阴线边框颜色
  349. borderColor: "#0CF49B", // 阳线边框颜色
  350. },
  351. },
  352. },
  353. // 副图热力矩阵
  354. {
  355. name: "热力矩阵",
  356. type: "heatmap",
  357. gridIndex: 1,
  358. xAxisIndex: 1,
  359. yAxisIndex: 1,
  360. data: normalHeatmap,
  361. coordinateSystem: "cartesian2d",
  362. tooltip: {
  363. trigger: "item",
  364. axisPointer: {
  365. type: 'cross', // 十字准星效果
  366. crossStyle: {
  367. color: '#999'
  368. }
  369. },
  370. // 覆盖全局 tooltip 配置
  371. formatter: function (params) {
  372. return `${params.value[2]}`; // 直接取数据中的第3个元素(数值)
  373. }
  374. },
  375. label: {
  376. normal: {
  377. show: true,
  378. color: "#fff", // 文本颜色
  379. formatter: function (params) {
  380. const value = params.value[2]; // 数值
  381. const colorType = params.value[3]; // 颜色类型
  382. // 使用 rich 富文本格式
  383. return `{${colorType}|${value}}`;
  384. },
  385. rich: {
  386. green: { color: "#27ae60", fontWeight: "bold" },
  387. purple: { color: "#8e44ad", fontWeight: "bold" },
  388. red: { color: "#FF0000", fontWeight: "bold" },
  389. yellow: { color: "#FFFF00", fontWeight: "bold" },
  390. normal: { color: "#fff" },
  391. },
  392. },
  393. },
  394. itemStyle: {
  395. normal: {
  396. color: "transparent",
  397. borderWidth: 2,
  398. },
  399. },
  400. emphasis: {
  401. itemStyle: {
  402. shadowBlur: 10,
  403. shadowColor: "rgba(0, 0, 0, 0.5)",
  404. },
  405. },
  406. },
  407. {
  408. name: '热力矩阵',
  409. type: 'heatmap',
  410. gridIndex: 1,
  411. xAxisIndex: 1,
  412. yAxisIndex: 1,
  413. data: rotateHeatmap,
  414. coordinateSystem: 'cartesian2d',
  415. tooltip: {
  416. trigger: 'item',
  417. // 覆盖全局 tooltip 配置
  418. formatter: function (params) {
  419. console.log('params', params)
  420. return `${params.value[2]}` // 直接取数据中的第3个元素(数值)
  421. }
  422. },
  423. label: {
  424. normal: {
  425. rotate: -60,
  426. show: true,
  427. color: 'white', // 文本颜色
  428. formatter: function (params) {
  429. const value = params.value[2] // 数值
  430. const colorType = params.value[3] // 颜色类型
  431. // 使用 rich 富文本格式
  432. return `{${colorType}|${value}}`
  433. },
  434. rich: {
  435. green: { color: '#27ae60', fontWeight: 'bold' },
  436. purple: { color: '#8e44ad', fontWeight: 'bold' },
  437. red: { color: '#FF0000', fontWeight: 'bold' },
  438. yellow: { color: '#FEB736FF', fontWeight: 'bold' },
  439. normal: { color: 'grey' }
  440. }
  441. }
  442. },
  443. itemStyle: {
  444. normal: {
  445. color: 'transparent',
  446. borderWidth: 2
  447. }
  448. },
  449. emphasis: {
  450. itemStyle: {
  451. shadowBlur: 10,
  452. shadowColor: 'rgba(0, 0, 0, 0.5)'
  453. }
  454. }
  455. },
  456. {
  457. name: "凸起",
  458. type: "line",
  459. xAxisIndex: 2,
  460. yAxisIndex: 2,
  461. data: lineData3.value,
  462. color: "black",
  463. lineStyle: {
  464. normal: {
  465. color: "red",
  466. },
  467. },
  468. symbol: "none",
  469. emphasis: {
  470. showSymbol: true,
  471. },
  472. },
  473. ],
  474. };
  475. // 添加DOM元素检查
  476. if (!KlineCanvs.value) {
  477. console.error('DOM元素未准备好,无法初始化ECharts');
  478. return;
  479. }
  480. // 创造echarts图
  481. if (KlineCanvsChart) {
  482. KlineCanvsChart.dispose();
  483. }
  484. KlineCanvsChart = echarts.init(KlineCanvs.value);
  485. KlineCanvsChart.setOption(KlineOption);
  486. // 添加图表交互事件监听器
  487. if (KlineCanvsChart) {
  488. // 监听数据缩放事件
  489. KlineCanvsChart.on('dataZoom', () => {
  490. if (window.handleChartInteractionStart) {
  491. window.handleChartInteractionStart();
  492. }
  493. });
  494. // 监听鼠标滚轮事件(缩放)
  495. KlineCanvsChart.on('mousewheel', () => {
  496. if (window.handleChartInteractionStart) {
  497. window.handleChartInteractionStart();
  498. }
  499. });
  500. // 监听鼠标按下事件(拖拽开始)
  501. KlineCanvsChart.on('mousedown', () => {
  502. if (window.handleChartInteractionStart) {
  503. window.handleChartInteractionStart();
  504. }
  505. });
  506. // 监听鼠标释放事件(拖拽结束)
  507. KlineCanvsChart.on('mouseup', () => {
  508. if (window.handleChartInteractionEnd) {
  509. window.handleChartInteractionEnd();
  510. }
  511. });
  512. // 监听图表点击事件
  513. KlineCanvsChart.on('click', () => {
  514. if (window.handleChartInteractionStart) {
  515. window.handleChartInteractionStart();
  516. }
  517. });
  518. }
  519. // 防抖函数,避免频繁触发resize
  520. const debounce = (func, wait) => {
  521. let timeout;
  522. return function executedFunction(...args) {
  523. const later = () => {
  524. clearTimeout(timeout);
  525. func(...args);
  526. };
  527. clearTimeout(timeout);
  528. timeout = setTimeout(later, wait);
  529. };
  530. };
  531. // 监听窗口大小变化,参考股市温度计的实现
  532. const resizeHandler = debounce(() => {
  533. if (KlineCanvsChart && !KlineCanvsChart.isDisposed()) {
  534. try {
  535. KlineCanvsChart.resize();
  536. console.log('情绪解码器图表已重新调整大小');
  537. } catch (error) {
  538. console.error('情绪解码器图表resize失败:', error);
  539. }
  540. }
  541. }, 100); // 100ms防抖延迟
  542. // 移除之前的监听器(如果存在)
  543. if (window.emotionDecodResizeHandler) {
  544. window.removeEventListener('resize', window.emotionDecodResizeHandler);
  545. }
  546. // 添加新的监听器
  547. window.addEventListener('resize', resizeHandler);
  548. // 存储resize处理器以便后续清理
  549. window.emotionDecodResizeHandler = resizeHandler;
  550. // 添加容器大小监听器
  551. if (KlineCanvs.value && window.ResizeObserver) {
  552. const containerObserver = new ResizeObserver(debounce(() => {
  553. if (KlineCanvsChart && !KlineCanvsChart.isDisposed()) {
  554. try {
  555. KlineCanvsChart.resize();
  556. console.log('情绪解码器容器大小变化,图表已调整');
  557. } catch (error) {
  558. console.error('情绪解码器容器resize失败:', error);
  559. }
  560. }
  561. }, 100));
  562. containerObserver.observe(KlineCanvs.value);
  563. window.emotionDecodContainerObserver = containerObserver;
  564. }
  565. }
  566. const windowHeight = ref(window.innerHeight);
  567. const updateHeight = () => {
  568. windowHeight.value = window.innerHeight; // 更新响应式变量
  569. };
  570. // 组件挂载时添加窗口高度监听
  571. onMounted(() => {
  572. // 避免重复添加监听器
  573. if (!window.emotionDecodHeightHandler) {
  574. window.addEventListener("resize", updateHeight);
  575. window.emotionDecodHeightHandler = updateHeight;
  576. }
  577. });
  578. onBeforeUnmount(() => {
  579. // 组件卸载时销毁图表
  580. if (KlineCanvsChart) {
  581. KlineCanvsChart.dispose();
  582. KlineCanvsChart = null;
  583. }
  584. // 移除窗口resize监听器
  585. if (window.emotionDecodResizeHandler) {
  586. window.removeEventListener('resize', window.emotionDecodResizeHandler);
  587. window.emotionDecodResizeHandler = null;
  588. }
  589. // 移除窗口高度监听器
  590. if (window.emotionDecodHeightHandler) {
  591. window.removeEventListener('resize', window.emotionDecodHeightHandler);
  592. window.emotionDecodHeightHandler = null;
  593. }
  594. // 清理容器观察器
  595. if (window.emotionDecodContainerObserver) {
  596. window.emotionDecodContainerObserver.disconnect();
  597. window.emotionDecodContainerObserver = null;
  598. }
  599. });
  600. </script>
  601. <style scoped>
  602. .qxjmqbox {
  603. height: auto;
  604. width: 100%;
  605. margin: 0 auto;
  606. }
  607. .qxjmqEcharts {
  608. width: 100%;
  609. height: 542px;
  610. margin: 0;
  611. /* top: 5rem; */
  612. box-sizing: border-box;
  613. overflow: hidden;
  614. }
  615. /* 手机端适配样式 */
  616. @media only screen and (max-width: 768px) {
  617. .qxjmqEcharts {
  618. width: 100%;
  619. height: 270px;
  620. margin: 0;
  621. /* top: 5rem; */
  622. }
  623. .qxjmqbox {
  624. height: auto;
  625. width: 90%;
  626. }
  627. }
  628. </style>