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.

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