因为工作需要使用大量echarts折线图和饼图,为了减少重复代码量以及方便使用,打算将echarts图封装成组件,这里以折线图为例,其他类型的图类似,甚至可以直接把本次封装的折线图作为其他类型使用,因为这次打算做一个通用封装。
基本要求
- 高度宽度可自定义方便调整不同容器中的图表样式
- 封装y轴名,单位位于英文括号内
- 封装series数据,可以直接将数据绑定在组件上
- 内置一条警戒线,单位取y轴名的单位
- 可自定义options覆盖内置封装(这是最关键的一点)
封装步骤
完整代码见:项目仓库
先看一下基本效果图:
<template>
<div ref="myChart" :style="{ height: height, width: width }"></div>
</template>
<script>
export default {
name: 'chart-line',
props: {
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '100%'
},
// y轴名
label: {
type: String,
default: ''
},
data: {
type: Array,
default: function () {
return [];
}
},
threshold: {
type: Number
},
options: {
type: Object
}
},
watch: {
data: {
deep: true,
handler(val) {
this.resizeHandler();
this.render(val);
}
}
},
mounted() {
this.initChart();
window.addEventListener('resize', this.resizeHandler, true);
},
data() {
return {
chart: null,
defaultOptions: {
// data中数据大于1条时展示图例
legend: {
show: this.data.length > 1
},
grid: {
containLabel: true
},
xAxis: {
type: 'time',
// x轴不显示刻度
axisTick: {
show: false
}
},
yAxis: {
type: 'value',
name: this.label
// 单位是 人 时y轴刻度最小间隔为1
// minInterval: this.label.match(/(([^)]+))/)[1] === '人' ? 1 : undefined
},
tooltip: {
trigger: 'axis',
// tooltip 框限制在图表的区域内
confine: true
}
},
// 内置一条markLine
defaultMarkLine: {
data: [
{
yAxis: this.threshold,
label: {
formatter: (e) => {
return this.label.match(/(([^)]+))/)
? `${e.value} ${this.label.match(/(([^)]+))/)[1]}`
: e.value;
}
}
}
],
symbol: 'none',
label: {
color: 'red'
},
lineStyle: {
color: 'red',
type: [5, 5]
}
}
};
},
methods: {
// 初始化渲染
initChart() {
this.chart = this.$echarts.init(this.$refs.myChart);
this.render();
},
// 重复渲染
render() {
if (this.chart) {
// 更新series数据
this.defaultOptions['series'] = this.data.map((row) => {
return {
name: row.name,
data: row.data,
type: 'line',
showSymbol: false
};
})
// 设置阈值
if (
this.threshold &&
this.defaultOptions.series.length > 0 &&
this.defaultOptions.series[0].data &&
this.defaultOptions.series[0].data.length > 0
) {
this.defaultOptions.series[0]['markLine'] = this.defaultMarkLine;
}
// 合并自定义options
this.options && this.recursiveMerge(this.defaultOptions, this.options);
console.log('options', this.defaultOptions);
this.chart.setOption(this.defaultOptions);
}
},
resizeHandler() {
this.chart && this.chart.resize();
},
// 递归合并
recursiveMerge(base, extend) {
if (!this.isPlainObject(base)) {
return extend;
}
for (const key in extend) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
base[key] =
this.isPlainObject(base[key]) && this.isPlainObject(extend[key])
? this.recursiveMerge(base[key], extend[key])
: extend[key];
}
return base;
},
// 判断输入是否为对象
isPlainObject(input) {
return input && typeof input === 'object' && !Array.isArray(input);
}
},
beforeDestroy() {
if (!this.chart) {
return;
}
window.removeEventListener('resize', this.resize);
this.chart.dispose();
this.chart = null;
}
};
</script>
使用
首先给一个容器存放组件,设置label(y轴名),放一些测试数据,设置一个阈值:
<div class="chart-block">
<chart-line label="连接时延(ms)" :data="testData" :threshold="30"></chart-line>
</div>
<script>
import ChartLine from "@/components/chart-line";
export default {
name: 'module-preview',
components: { ChartLine },
data() {
return {
testData: [
{
name: '上海',
data: [
['2022-08-06 17:20:20', 15],
['2022-08-06 17:30:20', 20],
['2022-08-06 17:40:20', 30],
['2022-08-06 17:50:20', 25],
['2022-08-06 18:00:20', 35],
['2022-08-06 18:10:20', 10],
]
},
{
name: '北京',
data: [
['2022-08-06 17:20:20', 25],
['2022-08-06 17:30:20', 10],
['2022-08-06 17:40:20', 20],
['2022-08-06 17:50:20', 15],
['2022-08-06 18:00:20', 30],
['2022-08-06 18:10:20', 35],
]
}
]
}
}
};
</script>
<style scoped>
.chart-block {
height: 300px;
}
</style>
如果这时候我想要对tooltip内容进行格式化或者想改变一下图例的样式又或者想让x轴显示刻度该怎么办呢? 直接在data中写一个自定义options就可以了:
<chart-line label="连接时延(ms)" :options="customOptions" :data="testData" :threshold="30"></chart-line>
customOptions: {
legend: {
icon: 'circle'
},
tooltip: {
valueFormatter: (value) => value + ' 毫秒'
},
xAxis: {
axisTick: {
show: true
}
},
}
效果如下:
总结
大部分代码应该都是比较好理解的,比较关键的地方在于如果使用这个组件时提供了自定义options,图表生成前则会合并默认封装的defaultOptions和自定义的options,这样遇到少部分需要特殊处理的图表时,只需要提供一个小型的options就可以了,不用再进行新的封装。
由于封装的组件只进行了一些基本的测试,因此可能还存在一些问题,如果任何疑问欢迎交流讨论。