写在开头
Echarts 一款强大的可视化库,当前已经迭代到 5.5.x 版本了,应用十分广泛。相信各位也都熟悉哈,俺就不过多介绍了。😋
这次要分享的是在Vue2项目中关于Echarts5的一些基本封装情况,请诸君按需食用。
前置准备
首先,咱先来创建一个 Vue2 项目:
js
vue create your-project-name
(在创建过程中,你可以根据提示选择合适的预设。)
再把 Echats 给整进来:
js
npm install echarts
启动项目:
js
npm run serve
基本使用
先来整一个基础的案例看看 Echarts 下载是否成功,在 App.vue
文件:
js
<template>
<div id="app">
<div id="chart" style="width: 600px; height: 400px"></div>
</div>
</template>
<script>
import * as echarts from "echarts/core";
// 按需引入!!!
import { PieChart } from "echarts/charts";
import { TitleComponent } from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";
// 注册
echarts.use([
PieChart,
CanvasRenderer,
TitleComponent,
]);
export default {
name: "App",
mounted() {
const chartDom = document.getElementById("chart");
const myChart = echarts.init(chartDom);
const options = {
title: {
text: "饼图示例",
},
series: [
{
type: "pie",
radius: "50%",
data: [
{ value: 1048, name: "语文" },
{ value: 735, name: "数学" },
{ value: 580, name: "英语" },
{ value: 484, name: "物理" },
{ value: 300, name: "化学" },
],
},
],
};
myChart.setOption(options);
},
};
</script>
如果能正常看饼图的渲染,则说明成功下载引入 Echarts 了。
这里需要特别注意的一点是,咱们使用 Echarts 的按需引入形式,需要从对应的包导出特定功能进行注册后才能使用,如果你使用中有发现异常,请注意查看控制台报错信息。👀
按需引入更多细节情况:传送门
基础封装
观察上述的基础使用,每次咱们创建一个图表时,基本都要引入相关的 Echarts 包并进行注册,接着获取 DOM,初始化 Echarts,这一系列操作颇为繁琐。而且,还要留意 Echarts 实例的相关情况,如实例不被 Vue
依赖收集,实例使用后需要进行销毁操作,整个过程反复且麻烦。还有如果同一个页面要创建多个图表,就更麻烦了,不仅要处理每个图表各自的实例问题,还要保证它们之间互不干扰。
Em......😗讲那么多,就是需要咱们来对 Echarts 进行一个基础封装,以便更好的使用。咱们在使用过程中其实最主要关注 options
参数就行,当 options
参数发生变化时,图表也自动随之改变,其他的操作基本都一样,全给它们整合封装在组件内去统一处理。
得,直接贴代码瞧瞧吧。👉👉👉
先创建 components/Echarts/install.js
文件:
js
/**
* @file 该文件用于按需引入 ECharts 的图表与组件
* @author xxx
* @recentUpdate 2024-11-05 15:00
* @notice Echarts图表与组件可在其源码下的 echarts/lib/export 找到
*/
import * as echarts from 'echarts/core';
import { PieChart } from 'echarts/charts';
import { TitleComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
// 图表
PieChart,
// 渲染器
CanvasRenderer,
// 组件
TitleComponent,
]);
export default function installer(dom) {
return dom ? echarts.init(dom) : echarts;
}
这个文件用于统一控制 Echarts 的图表和组件的引入与注册。
再创建 components/Echarts/ChartRender.vue
文件:
js
<template>
<div ref="container" class="container" />
</template>
<script>
import installer from './install.js';
export default {
props: {
options: {
type: Object,
required: true
}
},
data() {
return {
// 避免依赖收集
$chart: null,
timer: null
};
},
watch: {
options: {
handler() {
if (!this.options) return;
this.renderChart();
},
immediate: true,
deep: true
}
},
methods: {
/** @description 初始化或渲染图表 **/
renderChart() {
if (!this.$chart) {
this.initChart();
} else {
this.setOptions();
}
},
/** @description 初始化图表 **/
initChart() {
this.$nextTick(() => {
if (this.$chart) this.$chart.dispose();
this.$chart = installer(this.$refs.container);
/* 事件注册 - start */
// 点击事件,可以节流代理一下点击事件
this.$chart.on(
'click',
params => this.$emit('chartClick', params)
);
// 双击事件
this.$chart.on('dblclick', params => this.$emit('chartDblClick', params));
// 渲染完成事件
this.$chart.on('finished', () => this.$emit('chartFinished', this.$chart));
/* 事件注册 - end */
// 渲染图表
this.setOptions();
});
},
/** @description 渲染图表,进行防抖处理,防止短时间内options多次修改,造成重复渲染性能的浪费 **/
debouncedSetOptions() {
if (!this.$chart) return;
this.$chart.clear();
this.$chart.setOption(this.options, { notMerge: true });
},
setOptions() {
// 不可直接使用lodash的防抖,会造成初始渲染多组件的场景中,防抖函数内部的timer互相污染的问题
this.debounce(this.debouncedSetOptions, 200)();
},
/** @description 防抖函数 **/
debounce(func, delay) {
return (...args) => {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
func.apply(this, args);
this.timer = null;
}, delay);
};
}
},
beforeDestroy() {
this.timer = null;
if (!this.$chart) return;
this.$chart.off('click');
this.$chart.off('dblclick');
this.$chart.off('finished');
this.$chart.dispose();
this.$chart = null;
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
flex: 1;
}
</style>
在 App.vue
文件具体使用情况:
js
<template>
<div id="app">
<div style="width: 600px; height: 400px">
<ChartRender :options="options" />
</div>
</div>
</template>
<script>
import ChartRender from './components/Echarts/ChartRender.vue';
export default {
name: "App",
components: {
ChartRender
},
data() {
return {
options: {
title: {
text: "饼图示例",
},
series: [
{
type: "pie",
radius: "50%",
data: [
{ value: 1048, name: "语文" },
{ value: 735, name: "数学" },
{ value: 580, name: "英语" },
{ value: 484, name: "物理" },
{ value: 300, name: "化学" },
],
},
],
},
};
},
};
</script>
这样子就简单多了😀,如果需要多个图表,直接复制并提供多个 options
参数就能轻松完成。而且你更改去 options
参数,图表也会自动进行重新渲染,并且组件内部还进行了防抖处理,短时间内多次更改 options
参数仅会进行一次渲染。
可以如下简单测试是否有问题:
js
handleClick() {
this.options.title.text = '标题修改';
setTimeout(() => {
this.options.title.text = '标题修改2';
}, 100);
}
注意:🔉
在上述组件中使用了防抖函数(
debounce
),小编将函数中的timer
变量提升到data
里。这样做主要是为了避免在多个图表组件同时进行实例化 时,出现只有最后一个图表正常渲染的问题,因为这里我们需要保证每个图表组件都有独立的防抖处理,互相的timer
不能影响。
$chart
:通过以$
符号开头,能避免属性被 Vue 进行依赖收集,具体可以查看小编的另一篇文章。传送门
自适应尺寸
涉及到图表的需求,很多时候咱们都需要考虑尺寸自适应的问题;Echarts 也提供 echartsInstance.resize() 方法给我们处理尺寸变化的情况,但具体使用过程还需我们结合一些 JS
的 API 来监听处理,挺麻烦的。😑
小编这里选择使用 resize-detector 包来处理,它能帮助咱们跨浏览器、基于事件的形式检测元素大小的变化。
先安装它:
js
npm install resize-detector
具体在 ChartRender.vue
文件中使用:
js
<template>
<div ref="container" class="container" />
</template>
<script>
import installer from './install.js';
import { addListener, removeListener } from 'resize-detector';
export default {
// ...
methods: {
initChart() {
this.$nextTick(() => {
if (this.$chart) this.$chart.dispose();
this.$chart = installer(this.$refs.container);
// 尺寸自适应
addListener(this.$refs.container, this.resizeHandler);
// ...
});
},
/** @description 图表自适应尺寸,一样进行防抖处理 **/
resizeHandler() {
this.debounce(this.debounceResizeHandler, 200)();
},
debounceResizeHandler() {
this.$emit('chartResize');
this.$chart.resize();
},
// ...
},
beforeDestroy() {
this.timer = null;
if (!this.$chart) return;
// 移除
removeListener(this.$refs.container, this.resizeHandler);
// ...
}
};
</script>
效果:
导出图片
最后,咱们也可以将 Echarts 一些特有的功能做一层简单封装,方便后续的使用,如导出图表的图片。
js
/** @description 导出图片 **/
downloadPicture(name) {
if (!this.$chart) return;
const base64 = this.$chart.getDataURL({
type: 'png',
pixelRatio: 1.5,
backgroundColor: '#fff'
});
const fileName = name || new Date().getTime();
const link = document.createElement('a');
link.download = fileName;
link.style.display = 'none';
link.href = base64;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
}
具体使用:
js
<template>
<div id="app">
<div style="width: 100%; height: 400px;border: 1px solid red;">
<ChartRender ref="chartRender" :options="options" />
</div>
<button @click="handleClick">下载图片</button>
</div>
</template>
<script>
import ChartRender from './components/Echarts/ChartRender.vue';
export default {
// ...
methods: {
handleClick() {
this.$refs.chartRender.downloadPicture('这是图片名称');
}
}
};
</script>
这样子就很方便啦,更多其他功能就期待你自己去探究咯。😋
完整源码
ChartRender.vue
文件:
js
<template>
<div ref="container" class="container" />
</template>
<script>
import installer from './install.js';
import { addListener, removeListener } from 'resize-detector';
import { throttle, cloneDeep } from 'lodash';
export default {
props: {
options: {
type: Object,
required: true
}
},
data() {
return {
$chart: null,
timer: null
};
},
watch: {
options: {
handler() {
if (!this.options) return;
this.renderChart();
},
immediate: true,
deep: true
}
},
methods: {
renderChart() {
if (!this.$chart) {
this.initChart();
} else {
this.setOptions();
}
},
initChart() {
this.$nextTick(() => {
if (this.$chart) this.$chart.dispose();
this.$chart = installer(this.$refs.container);
addListener(this.$refs.container, this.resizeHandler);
this.$chart.on(
'click',
throttle(params => this.$emit('chartClick', params), 350, {
leading: true,
trailing: false
})
);
this.$chart.on('dblclick', params => this.$emit('chartDblClick', params));
this.$chart.on('finished', () => this.$emit('chartFinished', this.$chart));
this.setOptions();
});
},
debouncedSetOptions() {
if (!this.$chart) return;
const deepCloneOptions = cloneDeep(this.options);
this.$chart.clear();
this.$chart.setOption(deepCloneOptions, { notMerge: true });
},
setOptions() {
this.debounce(this.debouncedSetOptions, 200)();
},
resizeHandler() {
this.debounce(this.debounceResizeHandler, 200)();
},
debounceResizeHandler() {
this.$emit('chartResize');
this.$chart.resize();
},
/** @description 提供给外部手动调用渲染时使用 **/
setOptionsOutside(options) {
const deepCloneOptions = cloneDeep(options);
this.$chart.clear();
this.$chart.setOption(deepCloneOptions, { notMerge: true });
},
/** @description 清空当前实例,会移除实例中所有的组件和图表 **/
clearChart() {
this.$chart && this.$chart.clear();
},
downloadPicture(name) {
if (!this.$chart) return;
const base64 = this.$chart.getDataURL({
type: 'png',
pixelRatio: 1.5,
backgroundColor: '#fff'
});
const fileName = name || new Date().getTime();
const link = document.createElement('a');
link.download = fileName;
link.style.display = 'none';
link.href = base64;
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(link.href);
document.body.removeChild(link);
},
debounce(func, delay) {
return (...args) => {
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(() => {
func.apply(this, args);
this.timer = null;
}, delay);
};
}
},
beforeDestroy() {
this.timer = null;
if (!this.$chart) return;
removeListener(this.$refs.container, this.resizeHandler);
this.$chart.off('click');
this.$chart.off('dblclick');
this.$chart.off('finished');
this.$chart.dispose();
this.$chart = null;
}
};
</script>
<style scoped>
.container {
width: 100%;
height: 100%;
flex: 1;
}
</style>
install.js
文件:
js
/**
* @file 该文件用于按需引入 ECharts 的图表与组件
* @author xxx
* @recentUpdate 2024-11-05 15:00
* @notice Echarts图表与组件可在其源码下的 echarts/lib/export 找到
*/
import * as echarts from 'echarts/core';
import {
PieChart,
LineChart,
BarChart,
// ScatterChart,
// RadarChart,
// MapChart,
// FunnelChart,
// SunburstChart,
// CustomChart
} from 'echarts/charts';
import {
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
// DataZoomComponent,
// DataZoomInsideComponent,
// DataZoomSliderComponent,
// VisualMapComponent,
// PolarComponent,
// MarkLineComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
// 图表
PieChart,
LineChart,
BarChart,
// ScatterChart,
// RadarChart,
// MapChart,
// FunnelChart,
// SunburstChart,
// CustomChart,
// 渲染器
CanvasRenderer,
// 组件
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent
// DataZoomComponent,
// DataZoomInsideComponent,
// DataZoomSliderComponent,
// VisualMapComponent,
// PolarComponent,
// MarkLineComponent,
]);
export default function installer(dom) {
return dom ? echarts.init(dom) : echarts;
}
至此,本篇文章就写完啦,撒花撒花。