|
|
<template> <div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div> </template>
<script setup> import { ref, onMounted, onBeforeUnmount, toRef, reactive } from "vue"; import * as echarts from "echarts";
defineExpose({ initQXNLZHEcharts });
let qxnlzhqEchartsRef = ref(null); let qxnlzhqEchartsInstance = null;
let regions = reactive([]);
// 设置区域名称 位置
function getNameTop(min, max, regionMiidle) { // 获取整个图表的高度
const chartHeight = qxnlzhqEchartsInstance.getHeight(); // 60: 为x轴占的高度
return (max - Number(regionMiidle)) / (max - min) * (chartHeight - 60) } // 设置区域最大值 位置
function getNumberTop(min, max, regionMax) { // 获取整个图表的高度
const chartHeight = qxnlzhqEchartsInstance.getHeight(); // 60: 为x轴占的高度
return (max - Number(regionMax)) / (max - min) * (chartHeight - 60) }
// 生成图形标注(核心逻辑)
const generateGraphics = (min, max) => { return regions.flatMap((region) => { const middleY = (Number(region.min) + Number(region.max)) / 2; return [ // 区域名称(中间位置)
{ type: "text", left: '17', // 靠近y轴内侧
top: getNameTop(min, max, middleY), style: { text: region.name, fill: region.fontColor, fontSize: 14, fontWeight: "bold", }, z: 3, }, // y轴数值(顶部位置)
{ type: "text", left: '25', top: getNumberTop(min, max, region.max), // top: 100,
style: { text: region.max.toString(), fill: region.NumberColor, fontSize: 12, }, z: 3, }, ]; }); };
function initQXNLZHEcharts(kline, qxnlzhqData) { // 测试数据 !!! 删掉
// qxnlzhqData.topxh = ["2025/04/04", "2025/04/15"]
// qxnlzhqData.lowxh = ["2025/04/08", "2025/04/18"]
// qxnlzhqData.qixh = ["2025/04/10", "2025/04/21"]
if (qxnlzhqEchartsInstance) { qxnlzhqEchartsInstance.dispose(); } // 数据
let mixData = []; kline.forEach((element) => { let date = element[0]; let value = [element[1], element[2], element[3], element[4]]; mixData.push({ date, value, }); });
// 动态区域配置
// dd到zc 低吸区------情绪冰点区 ; zc到ht 关注区------认知潜伏区; ht到qs 回调区------多空消化区 ; qs到tp 拉升区------共识加速区;
// tp到js 突破区------情绪临界区 ; js到yl 警示区-------杠杆失衡区 ; yl到gg 风险区-------情绪熔断区;
regions = [ { min: qxnlzhqData.dd, max: qxnlzhqData.zc, name: "【情绪冰点区】", color: "#e7a5d6", fontColor: '#000', NumberColor: 'blue', }, { min: qxnlzhqData.zc, max: qxnlzhqData.ht, name: "【认知潜伏区】", color: "#f36587", fontColor: '#000', NumberColor: 'blue', }, { min: qxnlzhqData.ht, max: qxnlzhqData.qs, name: "【多空消化区】", color: "#e99883", fontColor: '#000', NumberColor: 'blue', }, { min: qxnlzhqData.qs, max: qxnlzhqData.tp, name: "【共识加速区】", color: "#f0db84", fontColor: '#000', NumberColor: 'red', }, { min: qxnlzhqData.tp, max: qxnlzhqData.js, name: "【情绪临界区】", color: "#dbeee3", fontColor: 'red', NumberColor: 'red', }, ]; // gg yl为-1 不绘制部分图表
if (Number(qxnlzhqData.yl) != -1) { regions.push( { min: qxnlzhqData.js, max: qxnlzhqData.yl, name: "【杠杆失衡区】", color: "#9ac2d8", fontColor: 'red', NumberColor: 'red', }, ) } if (Number(qxnlzhqData.gg) != -1) { regions.push( { min: qxnlzhqData.yl, max: qxnlzhqData.gg, name: "【情绪熔断区】", color: "#bce283", fontColor: 'red', NumberColor: 'red', }, ) }
// 计算止盈止损价格
const stopProfitPrice = Number(qxnlzhqData.cc) * 1.05; // 止盈价
const stopLossPrice = Number(qxnlzhqData.cc) * 0.97; // 止损价
// 确定起始和结束位置
const startIndex = Math.max(0, mixData.length - 17); // 创建完整数据数组
const takeProfitData = new Array(mixData.length).fill(null); const stopLossData = new Array(mixData.length).fill(null); // 填充显示区域的数据
for (var i = startIndex; i < mixData.length; i++) { takeProfitData[i] = stopProfitPrice; stopLossData[i] = stopLossPrice; }
// topxh、lowxh、qixh 对应k线染色
// 创建中间区域数据
const middleRangeData = []; const middleRangeData1 = []; const markPointData = []; mixData.forEach((item, index) => { const [open, close, low, high] = item.value; const rangeHeight = high - low; // const middleThirdStart = low + rangeHeight * (1/3);
// const middleThirdEnd = low + rangeHeight * (2/3);
let color = null; if (qxnlzhqData.topxh.includes(item.date)) { color = '#000000'; // 黑色
} else if (qxnlzhqData.lowxh.includes(item.date)) { color = '#1E90FF'; // 蓝色
} // 添加中间区域数据
if (color) { middleRangeData.push({ value: [index, close > open ? (close - open) : (open - close)], // 修正数据格式
itemStyle: { normal: { color: color } }, }); middleRangeData1.push({ value: [index, close > open ? open : close], // 修正数据格式
itemStyle: { normal: { color: 'transparent' } }, }); } else { middleRangeData.push(null); middleRangeData1.push(null); }
// 添加文字标记数据
if (qxnlzhqData.qixh.includes(item.date)) { markPointData.push({ name: '起', coord: [index, (open + close) / 2], itemStyle: { normal: { color: 'rgba(0,0,0,0)' // 标记点透明
} }, label: { normal:{ show: true, position: 'inside', formatter: '起', textStyle: { color: '#FF0000', fontSize: 10, // fontWeight: 'bold'
} } } }); } });
// 初始化图表
qxnlzhqEchartsInstance = echarts.init(qxnlzhqEchartsRef.value); let option; // 设置图表配置
option = { xAxis: { type: "category", data: mixData.map((item) => item.date), axisLabel: { rotate: 45, // 日期旋转45度防止重叠
}, }, yAxis: { scale: true, splitLine: { show: false, }, axisLabel: { // 刻度标签
show: false // 不显示刻度标签
}, axisTick: { // 刻度线
show: false // 不显示刻度线
}, min: qxnlzhqData.dd < stopLossPrice * 0.98 ? Math.floor(qxnlzhqData.dd) : Math.floor(stopLossPrice * 0.98), max: qxnlzhqData.yl > stopProfitPrice * 1.02 ? Math.ceil(qxnlzhqData.yl) : Math.ceil(stopProfitPrice * 1.02), }, // 自定义区域名称和区域范围值 位置
graphic: generateGraphics(qxnlzhqData.dd < stopLossPrice * 0.98 ? Math.floor(qxnlzhqData.dd) : Math.floor(stopLossPrice * 0.98),qxnlzhqData.yl > stopProfitPrice * 1.02 ? Math.ceil(qxnlzhqData.yl) : Math.ceil(stopProfitPrice * 1.02)), series: [ { type: "candlestick", data: mixData.map((item) => item.value), z: 1, markPoint: { symbol: 'circle', symbolSize: 10, data: markPointData, z: 5 // 确保标记显示在最上层
}, itemStyle: { normal: { // 阳线样式(收盘 > 开盘)
color: 'transparent', // 阳线色
color0: '#008080', // 阴线色
borderColor: '#ff0783', // 阳线边框色(红)
borderColor0: '#008080', // 阴线边框色(绿)
} }, // 实现 分区域背景色
markArea: { silent: true, data: regions.map((region) => [ { yAxis: region.min, itemStyle: { normal: { color: region.color } }, }, { yAxis: region.max }, ]), }, }, { name: '中间区域', type: 'bar', stack: 'total', data: middleRangeData1, barWidth: '20%', barCategoryGap: '40%', itemStyle: { normal: { color: 'rgba(0,0,0,0)' // 默认透明
} }, z:2 }, // 中间区域染色
{ name: '中间区域', type: 'bar', stack: 'total', data: middleRangeData, barWidth: '20%', barCategoryGap: '40%', itemStyle: { normal: { color: 'rgba(0,0,0,0)' // 默认透明
} }, z:2 }, { name: '止盈线', type: 'line', data: takeProfitData, symbol: 'none', lineStyle: { normal: { color: '#ff80ff', // 蓝色
width: 2, type: 'solid' } }, markPoint: { symbol: 'circle', symbolSize: 1, data: [ { coord: [mixData.map((item) => item.value).length - 1, stopProfitPrice], itemStyle: { color: '#ff80ff' }, label: { normal: { show: true, position: 'bottom', formatter: `{text|止盈: ${stopProfitPrice}}`, rich: { text: { color: '#820a06', fontSize: 14, fontWeight: 'bold' } } } } } ] } }, { name: '止损线', type: 'line', data: stopLossData, symbol: 'none', lineStyle: { normal: { color: '#080bfd', // 红色
width: 2, type: 'solid' } }, markPoint: { symbol: 'circle', symbolSize: 1, data: [ { coord: [mixData.map((item) => item.value).length - 1, stopLossPrice], itemStyle: { color: '#080bfd' }, label: { normal: { show: true, position: 'bottom', formatter: `{text|止损: ${stopLossPrice}}`, rich: { text: { color: '#080bfd', fontSize: 14, fontWeight: 'bold' } } } } } ] } }, ], grid: { left: "0", right: "10", top: '10', bottom: "0", containLabel: true, }, }; // 应用配置
qxnlzhqEchartsInstance.setOption(option); // 监听窗口大小变化,调整图表尺寸
window.addEventListener('resize', () => { qxnlzhqEchartsInstance.resize() }) }
onBeforeUnmount(() => { // 组件卸载时销毁图表
if (qxnlzhqEchartsInstance) { qxnlzhqEchartsInstance.dispose(); } }); </script> <style scoped> #qxnlzhqEcharts { width: 100%; height: 700px; } </style>
|