264 lines
5.9 KiB
Vue
264 lines
5.9 KiB
Vue
<script lang="ts" setup>
|
||
import { nextTick, watch, onMounted, onBeforeUnmount, ref, markRaw } from 'vue';
|
||
import * as echarts from 'echarts/core';
|
||
import {
|
||
TitleComponent,
|
||
ToolboxComponent,
|
||
TooltipComponent,
|
||
GridComponent,
|
||
DataZoomComponent,
|
||
LegendComponent,
|
||
} from 'echarts/components';
|
||
import { LineChart } from 'echarts/charts';
|
||
import { UniversalTransition } from 'echarts/features';
|
||
import { CanvasRenderer } from 'echarts/renderers';
|
||
import { parseSizeFromKBs, parseSizeFromMB } from '@/utils/parse-utils';
|
||
|
||
echarts.use([
|
||
TitleComponent,
|
||
ToolboxComponent,
|
||
TooltipComponent,
|
||
GridComponent,
|
||
DataZoomComponent,
|
||
LegendComponent,
|
||
LineChart,
|
||
CanvasRenderer,
|
||
UniversalTransition,
|
||
]);
|
||
|
||
const props = defineProps({
|
||
/**
|
||
* 图表主题
|
||
*
|
||
* 'dark' | 'light'
|
||
*/
|
||
theme: {
|
||
type: String,
|
||
default: 'light', // 'dark' | 'light'
|
||
},
|
||
/**宽度,默认100% */
|
||
width: {
|
||
type: String,
|
||
default: '100%',
|
||
},
|
||
/**高度 */
|
||
height: {
|
||
type: String,
|
||
default: '200px',
|
||
},
|
||
/**底部时间轴 */
|
||
dataZoom: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
/**
|
||
* option: { title , xDatas, yDatas, formatStr, yAxis, grid, tooltip}
|
||
*
|
||
* grid 图偏移 列如{ left: '15%', right: '15%', bottom: '20%' }
|
||
* formatStr 支持 'KB/s'、'MB'、自定义格式
|
||
*/
|
||
option: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
});
|
||
|
||
const seriesStyle = [
|
||
{
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{
|
||
offset: 0,
|
||
color: 'rgba(22, 119, 255, .5)',
|
||
},
|
||
{
|
||
offset: 1,
|
||
color: 'rgba(22, 119, 255, 0)',
|
||
},
|
||
]),
|
||
},
|
||
{
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{
|
||
offset: 0,
|
||
color: 'rgba(82, 196, 26, .5)',
|
||
},
|
||
{
|
||
offset: 1,
|
||
color: 'rgba(82, 196, 26, 0)',
|
||
},
|
||
]),
|
||
},
|
||
{
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{
|
||
offset: 0,
|
||
color: 'rgba(250, 173, 20, .5)',
|
||
},
|
||
{
|
||
offset: 1,
|
||
color: 'rgba(250, 173, 20, 0)',
|
||
},
|
||
]),
|
||
},
|
||
{
|
||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||
{
|
||
offset: 0,
|
||
color: 'rgba(255, 77, 79, 0.5)',
|
||
},
|
||
{
|
||
offset: 1,
|
||
color: 'rgba(255, 77, 79, 0)',
|
||
},
|
||
]),
|
||
},
|
||
];
|
||
|
||
let newChart: echarts.ECharts;
|
||
const chartDom = ref<HTMLElement | undefined>(undefined);
|
||
|
||
/**初始化渲染图表 */
|
||
function handleRanderChart(container: HTMLElement | undefined) {
|
||
if (!container || !props.option.yDatas) return;
|
||
if (!newChart) {
|
||
newChart = markRaw(echarts.init(container, props.theme));
|
||
}
|
||
|
||
const series: any = [];
|
||
const yDatasLen = props.option.yDatas.length;
|
||
props.option.yDatas.forEach((item: any, index: number) => {
|
||
series.push({
|
||
name: item?.name,
|
||
type: 'line',
|
||
areaStyle: seriesStyle[index % yDatasLen],
|
||
data: item?.data,
|
||
showSymbol: false,
|
||
yAxisIndex: item.yAxisIndex ? 1 : null,
|
||
});
|
||
});
|
||
|
||
// 右侧y轴辅助线
|
||
const yAxis: any = [];
|
||
if (props.option.yAxis && props.option.yAxis.length > 0) {
|
||
props.option.yAxis.forEach((item: any) => {
|
||
yAxis.push({
|
||
splitLine: {
|
||
show: true,
|
||
//分隔辅助线
|
||
lineStyle: {
|
||
type: 'dashed', //线的类型 虚线0
|
||
opacity: props.theme === 'dark' ? 0.1 : 1, //透明度
|
||
},
|
||
},
|
||
...item,
|
||
});
|
||
});
|
||
}
|
||
|
||
// 把配置和数据放这里
|
||
const option = {
|
||
title: [
|
||
{
|
||
left: 'center',
|
||
text: props.option.title,
|
||
show: props.option.title,
|
||
},
|
||
],
|
||
zlevel: 1,
|
||
z: 1,
|
||
tooltip: props.option.tooltip || {
|
||
trigger: 'axis',
|
||
formatter: function (datas: any) {
|
||
let res = datas[0].name + '<br/>';
|
||
// 拼接值的格式
|
||
switch (props.option.formatStr) {
|
||
case 'KB/s':
|
||
for (const item of datas) {
|
||
res += `${item.marker} ${item.seriesName}:${parseSizeFromKBs(
|
||
item.data
|
||
)}<br/>`;
|
||
}
|
||
break;
|
||
case 'MB':
|
||
for (const item of datas) {
|
||
res += `${item.marker} ${item.seriesName}:${parseSizeFromMB(
|
||
item.data
|
||
)}<br/>`;
|
||
}
|
||
break;
|
||
default:
|
||
for (const item of datas) {
|
||
res += `${item.marker} ${item.seriesName}:${item.data}${props.option.formatStr}<br/>`;
|
||
}
|
||
break;
|
||
}
|
||
return res;
|
||
},
|
||
},
|
||
grid: props.option.grid || { left: '8%', right: '8%', bottom: '20%' },
|
||
legend: props.option.legend || {
|
||
right: 10,
|
||
itemWidth: 8,
|
||
textStyle: {
|
||
color: '#646A73',
|
||
},
|
||
icon: 'circle',
|
||
},
|
||
xAxis: { data: props.option.xDatas, boundaryGap: false },
|
||
yAxis: props.option.yAxis
|
||
? yAxis
|
||
: {
|
||
name: '( ' + props.option.formatStr + ' )',
|
||
splitLine: {
|
||
//分隔辅助线
|
||
lineStyle: {
|
||
type: 'dashed', //线的类型 虚线0
|
||
opacity: props.theme === 'dark' ? 0.1 : 1, //透明度
|
||
},
|
||
},
|
||
},
|
||
series: series,
|
||
dataZoom: [{ startValue: props.option?.xDatas[0], show: props.dataZoom }],
|
||
};
|
||
|
||
// 渲染数据
|
||
option && newChart.setOption(option, true);
|
||
|
||
// 创建 ResizeObserver 实例
|
||
var observer = new ResizeObserver(entries => {
|
||
if (newChart) {
|
||
newChart.resize();
|
||
}
|
||
});
|
||
// 监听元素大小变化
|
||
observer.observe(container);
|
||
}
|
||
|
||
watch(
|
||
() => props.option,
|
||
val => {
|
||
if (val) {
|
||
nextTick(() => {
|
||
handleRanderChart(chartDom.value);
|
||
});
|
||
}
|
||
}
|
||
);
|
||
|
||
onMounted(() => {
|
||
nextTick(() => {
|
||
handleRanderChart(chartDom.value);
|
||
});
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
newChart?.dispose();
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div ref="chartDom" :style="{ height: height, width: width }"></div>
|
||
</template>
|
||
|
||
<style lang="less" scoped></style>
|