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.

531 lines
14 KiB

2 months ago
2 months ago
2 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. const dealData = spliteDate(data);
  64. // 给配置项
  65. const KlineOption = {
  66. tooltip: {
  67. trigger: 'item', // 触发类型 坐标轴触发
  68. axisPointer: {
  69. type: 'cross', // 十字准星效果
  70. crossStyle: {
  71. color: '#999'
  72. }
  73. },
  74. formatter: function (params) {
  75. const date = params.name
  76. // 开收低高分别取参数的第2到第5个数
  77. const open = params.data[1]
  78. const close = params.data[2]
  79. const low = params.data[3]
  80. const high = params.data[4]
  81. return `日期: ${date}<br/>开盘价: ${open}<br/>收盘价: ${close}<br/>最低价: ${low}<br/>最高价: ${high}`
  82. }
  83. },
  84. //控制坐标轴
  85. grid: [
  86. {
  87. top: "5%",
  88. height: window.innerWidth <= 768 ? "35%" : "40%",
  89. width: window.innerWidth <= 768 ? "70%" : "80%",
  90. left: window.outerWidth <= 768 ? "20%" : "10%",
  91. },
  92. {
  93. top: window.innerWidth <= 768 ? "40%" :"45%",
  94. height: "35%",
  95. width: window.innerWidth <= 768 ? "70%" : "80%",
  96. left: window.outerWidth <= 768 ? "20%" : "10%",
  97. },
  98. {
  99. top: window.innerWidth <= 768 ? "75%" : "80%",
  100. height: "2%",
  101. width: window.innerWidth <= 768 ? "70%" : "80%",
  102. left: window.outerWidth <= 768 ? "20%" : "10%",
  103. },
  104. ],
  105. visualMap: [
  106. {
  107. show: false, // 不显示控制条
  108. seriesIndex: 1, // 作用于热力图的系列
  109. min: 0,
  110. max: 2000, // 根据您的数据范围调整
  111. calculable: true,
  112. orient: "horizontal",
  113. left: "center",
  114. bottom: "15%",
  115. inRange: {
  116. color: ["transparent"],
  117. },
  118. },
  119. ],
  120. // 横坐标内容
  121. xAxis: [
  122. {
  123. type: "category",
  124. gridIndex: 0,
  125. data: dealData.categoryData,
  126. axisPointer: {
  127. show: true,
  128. type: "line",
  129. label: {
  130. show: true,
  131. backgroundColor: 'rgba(0,191,255)',
  132. color: 'black'
  133. },
  134. },
  135. axisTick: { show: false }, // 隐藏刻度线
  136. axisLabel: { show: false, rotate: 45 }, // 隐藏刻度标签
  137. axisLine: {
  138. show: true,
  139. lineStyle: {
  140. color: "white",
  141. },
  142. },
  143. },
  144. {
  145. type: "category",
  146. gridIndex: 1,
  147. data: dealData.categoryData,
  148. axisTick: { show: false }, // 隐藏刻度线
  149. axisLabel: { show: false, rotate: 45 }, // 隐藏刻度标签
  150. splitLine: {
  151. show: true,
  152. lineStyle: {
  153. color: "white",
  154. // 间隔线类型实线
  155. type: "solid",
  156. },
  157. interval: 0,
  158. },
  159. },
  160. {
  161. type: "category",
  162. gridIndex: 2,
  163. data: dealData.categoryData,
  164. axisLine: { lineStyle: { color: "white" } },
  165. axisPointer: {
  166. show: false,
  167. label: {
  168. show: false,
  169. },
  170. type: "line",
  171. },
  172. axisTick: {
  173. show: true,
  174. alignWithLabel: true, // 刻度线与标签对齐
  175. lineStyle: {
  176. color: "white", // 与十字线颜色保持一致
  177. width: 1,
  178. type: "dashed" // 与十字线样式保持一致
  179. }
  180. },
  181. },
  182. ],
  183. yAxis: [
  184. {
  185. scale: true,
  186. axisLabel: {
  187. formatter: function (value) {
  188. return value;
  189. },
  190. },
  191. axisLine: {
  192. show: true,
  193. lineStyle: { color: "white" },
  194. },
  195. splitLine: {
  196. show: false,
  197. },
  198. axisPointer: {
  199. show: true,
  200. label: {
  201. show: true,
  202. backgroundColor: 'rgba(0,255,127)',
  203. color: 'black'
  204. },
  205. type: "line",
  206. },
  207. },
  208. {
  209. gridIndex: 1,
  210. type: "category",
  211. data: [0, 1, 2, 3], // 倒序显示
  212. axisLine: { lineStyle: { color: "white" } },
  213. axisLabel: {
  214. show: false, // 显示刻度标签
  215. color: "#fff", // 白色文字
  216. backgroundColor: "transparent",
  217. fontSize: 12,
  218. margin: 8,
  219. },
  220. axisPointer: {
  221. show: true,
  222. label: {
  223. show: false,
  224. },
  225. type: "line",
  226. },
  227. axisTick: { show: false }, // 隐藏刻度线
  228. splitLine: {
  229. show: true,
  230. lineStyle: {
  231. color: "#8392A5",
  232. width: 1,
  233. type: "solid",
  234. },
  235. interval: 0, // 强制显示间隔
  236. },
  237. },
  238. {
  239. gridIndex: 2,
  240. type: "value",
  241. axisLine: {
  242. show: true,
  243. lineStyle: { color: "white" },
  244. },
  245. splitLine: {
  246. show: !1,
  247. },
  248. axisTick: { show: false }, // 隐藏刻度线
  249. axisLabel: { show: false }, // 隐藏刻度标签
  250. axisPointer: {
  251. show: false,
  252. label: {
  253. show: false,
  254. },
  255. type: "line",
  256. },
  257. },
  258. ],
  259. // 下拉条
  260. dataZoom: [
  261. {
  262. textStyle: {
  263. color: "white", // 字体颜色
  264. },
  265. // handleIcon:
  266. // "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",
  267. // handleSize: "80%",
  268. dataBackground: {
  269. areaStyle: {
  270. color: "#8392A5",
  271. },
  272. lineStyle: {
  273. opacity: 0.8,
  274. color: "#8392A5",
  275. },
  276. },
  277. xAxisIndex: [0, 1, 2],
  278. // handleStyle: {
  279. // color: "#fff",
  280. // shadowBlur: 3,
  281. // shadowColor: "rgba(0, 0, 0, 0.6)",
  282. // shadowOffsetX: 2,
  283. // shadowOffsetY: 2,
  284. // },
  285. start: 50, // 默认展示后半部分数据
  286. end: 100,
  287. bottom: window.innerWidth <= 768 ? "5%" : "7%", // 下移数据缩放滑块
  288. },
  289. // {
  290. // show: true,
  291. // type: "slider",
  292. // xAxisIndex: [0, 1, 2],
  293. // bottom: "0%",
  294. // textStyle: {
  295. // color: "white",
  296. // },
  297. // },
  298. {
  299. type: "inside",
  300. xAxisIndex: [0, 1, 2],
  301. filterMode: "filter",
  302. },
  303. ],
  304. series: [
  305. {
  306. type: "candlestick",
  307. name: "\u65e5K",
  308. // 数据
  309. data: dealData.value,
  310. itemStyle: {
  311. normal: {
  312. color0: "red", // 阴线颜色
  313. color: "#0CF49B", // 阳线颜色
  314. borderColor0: "#FD1050", // 阴线边框颜色
  315. borderColor: "#0CF49B", // 阳线边框颜色
  316. },
  317. },
  318. },
  319. // 副图热力矩阵
  320. {
  321. name: "热力矩阵",
  322. type: "heatmap",
  323. gridIndex: 1,
  324. xAxisIndex: 1,
  325. yAxisIndex: 1,
  326. data: processedHeatmap,
  327. coordinateSystem: "cartesian2d",
  328. tooltip: {
  329. trigger: "item",
  330. axisPointer: {
  331. type: 'cross', // 十字准星效果
  332. crossStyle: {
  333. color: '#999'
  334. }
  335. },
  336. // 覆盖全局 tooltip 配置
  337. formatter: function (params) {
  338. return `${params.value[2]}`; // 直接取数据中的第3个元素(数值)
  339. }
  340. },
  341. label: {
  342. normal: {
  343. show: true,
  344. color: "#fff", // 文本颜色
  345. formatter: function (params) {
  346. const value = params.value[2]; // 数值
  347. const colorType = params.value[3]; // 颜色类型
  348. // 使用 rich 富文本格式
  349. return `{${colorType}|${value}}`;
  350. },
  351. rich: {
  352. green: { color: "#27ae60", fontWeight: "bold" },
  353. purple: { color: "#8e44ad", fontWeight: "bold" },
  354. red: { color: "#FF0000", fontWeight: "bold" },
  355. yellow: { color: "#FFFF00", fontWeight: "bold" },
  356. normal: { color: "#fff" },
  357. },
  358. },
  359. },
  360. itemStyle: {
  361. normal: {
  362. color: "transparent",
  363. borderWidth: 2,
  364. },
  365. },
  366. emphasis: {
  367. itemStyle: {
  368. shadowBlur: 10,
  369. shadowColor: "rgba(0, 0, 0, 0.5)",
  370. },
  371. },
  372. },
  373. {
  374. name: "凸起",
  375. type: "line",
  376. xAxisIndex: 2,
  377. yAxisIndex: 2,
  378. data: lineData3.value,
  379. color: "black",
  380. lineStyle: {
  381. normal: {
  382. color: "red",
  383. },
  384. },
  385. symbol: "none",
  386. emphasis: {
  387. showSymbol: true,
  388. },
  389. },
  390. ],
  391. };
  392. // 创造echarts图
  393. if (KlineCanvsChart) {
  394. KlineCanvsChart.dispose();
  395. }
  396. KlineCanvsChart = echarts.init(KlineCanvs.value);
  397. KlineCanvsChart.setOption(KlineOption);
  398. // 防抖函数,避免频繁触发resize
  399. const debounce = (func, wait) => {
  400. let timeout;
  401. return function executedFunction(...args) {
  402. const later = () => {
  403. clearTimeout(timeout);
  404. func(...args);
  405. };
  406. clearTimeout(timeout);
  407. timeout = setTimeout(later, wait);
  408. };
  409. };
  410. // 监听窗口大小变化,参考股市温度计的实现
  411. const resizeHandler = debounce(() => {
  412. if (KlineCanvsChart && !KlineCanvsChart.isDisposed()) {
  413. try {
  414. KlineCanvsChart.resize();
  415. console.log('情绪解码器图表已重新调整大小');
  416. } catch (error) {
  417. console.error('情绪解码器图表resize失败:', error);
  418. }
  419. }
  420. }, 100); // 100ms防抖延迟
  421. // 移除之前的监听器(如果存在)
  422. if (window.emotionDecodResizeHandler) {
  423. window.removeEventListener('resize', window.emotionDecodResizeHandler);
  424. }
  425. // 添加新的监听器
  426. window.addEventListener('resize', resizeHandler);
  427. // 存储resize处理器以便后续清理
  428. window.emotionDecodResizeHandler = resizeHandler;
  429. // 添加容器大小监听器
  430. if (KlineCanvs.value && window.ResizeObserver) {
  431. const containerObserver = new ResizeObserver(debounce(() => {
  432. if (KlineCanvsChart && !KlineCanvsChart.isDisposed()) {
  433. try {
  434. KlineCanvsChart.resize();
  435. console.log('情绪解码器容器大小变化,图表已调整');
  436. } catch (error) {
  437. console.error('情绪解码器容器resize失败:', error);
  438. }
  439. }
  440. }, 100));
  441. containerObserver.observe(KlineCanvs.value);
  442. window.emotionDecodContainerObserver = containerObserver;
  443. }
  444. }
  445. const windowHeight = ref(window.innerHeight);
  446. const updateHeight = () => {
  447. windowHeight.value = window.innerHeight; // 更新响应式变量
  448. };
  449. // 组件挂载时添加窗口高度监听
  450. onMounted(() => {
  451. // 避免重复添加监听器
  452. if (!window.emotionDecodHeightHandler) {
  453. window.addEventListener("resize", updateHeight);
  454. window.emotionDecodHeightHandler = updateHeight;
  455. }
  456. });
  457. onBeforeUnmount(() => {
  458. // 组件卸载时销毁图表
  459. if (KlineCanvsChart) {
  460. KlineCanvsChart.dispose();
  461. KlineCanvsChart = null;
  462. }
  463. // 移除窗口resize监听器
  464. if (window.emotionDecodResizeHandler) {
  465. window.removeEventListener('resize', window.emotionDecodResizeHandler);
  466. window.emotionDecodResizeHandler = null;
  467. }
  468. // 移除窗口高度监听器
  469. if (window.emotionDecodHeightHandler) {
  470. window.removeEventListener('resize', window.emotionDecodHeightHandler);
  471. window.emotionDecodHeightHandler = null;
  472. }
  473. // 清理容器观察器
  474. if (window.emotionDecodContainerObserver) {
  475. window.emotionDecodContainerObserver.disconnect();
  476. window.emotionDecodContainerObserver = null;
  477. }
  478. });
  479. </script>
  480. <style scoped>
  481. .qxjmqbox {
  482. height: auto;
  483. width: 100%;
  484. margin: 0 auto;
  485. }
  486. .qxjmqEcharts {
  487. width: 100%;
  488. height: 542px;
  489. margin: 0;
  490. /* top: 5rem; */
  491. box-sizing: border-box;
  492. overflow: hidden;
  493. }
  494. /* 手机端适配样式 */
  495. @media only screen and (max-width: 768px) {
  496. .qxjmqEcharts {
  497. width: 100%;
  498. height: 270px;
  499. margin: 0;
  500. /* top: 5rem; */
  501. }
  502. .qxjmqbox {
  503. height: auto;
  504. width: 90%;
  505. }
  506. }
  507. </style>