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.
 
 
 
 

240 lines
5.7 KiB

<template>
<div ref="chartContainer" class="wave-line-chart-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
// 定义组件属性
interface Props {
chartData?: Array<{ name: string; value: number }>
title?: string
color?: string
magnifyMode?: boolean
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
color: '#1296db',
magnifyMode: false
})
// 图表容器引用和实例
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
// 初始化图表
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
// 更新图表
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
// 创建波浪效果的数据点
const waveData = props.chartData.map(item => {
return {
...item,
value: item.value
}
})
const option = {
title: {
// text: props.title,
textStyle: {
color: '#fff',
fontSize: props.magnifyMode ? 24 : 14
},
left: 'center'
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff',
fontSize: props.magnifyMode ? 20 : 12
}
},
xAxis: {
type: 'category',
data: props.chartData.map(item => item.name),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
fontSize: props.magnifyMode ? 18 : 10,
rotate: 0,
interval: Math.ceil(props.chartData.length / 12), // 减少间隔以显示更多标签
formatter: (value: string) => {
// 更灵活的时间格式化
if (value.includes(':')) {
const parts = value.split(':');
if (parts.length >= 2) {
// 如果是完整的时间格式 HH:mm:ss
if (parts[2] && parts[2] === '00') {
return parts[0] + ':' + parts[1];
}
// 如果是 HH:mm 格式
return parts[0] + ':' + parts[1];
}
}
return value;
},
margin: 10
}
},
yAxis: {
type: 'value',
// 设置 Y 轴最小值略低于数据中的最小值,以提供更好的可视化效果
min: (value: any) => {
if (props.chartData && props.chartData.length > 0) {
const minValue = props.chartData[0].value
// 计算一个合适的最小值,略低于实际最小值
// const range = value.max - minValue;
return minValue; // 最小值不低于0
}
return undefined;
},
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
fontSize: props.magnifyMode ? 18 : 10
}
},
series: [
{
data: waveData.map(item => item.value),
type: 'line',
smooth: true,
showSymbol: true,
symbol: 'circle',
symbolSize: props.magnifyMode ? 8 : 4,
lineStyle: {
color: props.color,
width: props.magnifyMode ? 4 : 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.color + '80' // 添加透明度
},
{
offset: 1,
color: props.color + '00' // 透明
}
]
}
},
// 添加波浪效果的特殊配置
emphasis: {
focus: 'series'
},
// 显示数据点和数值
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: props.magnifyMode ? 16 : 10,
formatter: '{c}',
distance: props.magnifyMode ? 10 : 5
}
},
// 添加一个辅助系列来增强波浪视觉效果
{
data: props.chartData.map(item => item.value),
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
color: props.color + '60',
width: props.magnifyMode ? 2 : 1,
type: 'dashed'
},
// 辅助系列不显示数据标签以避免重叠
label: {
show: false
}
}
],
grid: {
left: '1%',
right: '1%',
top: props.title ? (props.magnifyMode ? '10%' : '15%') : '5%',
bottom: '1%',
containLabel: true
}
}
chartInstance.setOption(option)
}
// 监听数据变化
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
// 窗口大小改变时重绘图表
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
// 组件挂载时初始化图表
onMounted(() => {
// 延迟初始化,确保DOM已经完全渲染
setTimeout(() => {
initChart()
}, 200)
window.addEventListener('resize', handleResize)
})
// 组件销毁前清理资源
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.wave-line-chart-container {
width: 100%;
height: 100%;
min-height: 0;
}
</style>