大家好,我是一诺。今天我们将深入探讨 ECharts,这个功能强大的数据可视化库。
无论你是已经在使用 ECharts,还是正计划用它来创建一些炫酷的图表,这篇文章都会对你有所帮助。
我们将从渲染模式开始,逐步深入到如何封装通用组件,并探讨一些高级图表配置,比如地图伪3D效果、轮毂高亮、自定义Tooltip悬浮框以及富文本等。
这是一诺的个人知识库,包含前端、uniapp、node、vue大屏开发的技术点👇 https://t.zsxq.com/5Gxcj
1. 渲染模式:Canvas 还是 SVG?
ECharts 从 5.0 版本开始支持两种渲染模式:Canvas 和 SVG。
那么问题来了,什么时候该用 Canvas,什么时候该用 SVG 呢?
咱们先看下两种模式使用场景
场景 | Canvas | SVG |
数据量 | 适合大量数据,性能好 | 数据量较小时表现更佳 |
交互复杂度 | 复杂交互流畅,适合动态效果 | 交互稍弱,适合静态展示 |
清晰度 | 适合屏幕显示,导出可能模糊 | 高清晰度,导出效果极佳 |
图层效果 | 适合简单图层叠加 | 适合复杂图层效果(如阴影、渐变) |
简单总结一下:
Canvas,适合大数据量,性能较好。比如绘制一个包含成千上万数据点的折线图、散点图。
SVG,适合需要高清晰度的场景。尤其是当你需要导出图表时,SVG 的矢量特性会让图表看起来更加清晰。
2. 封装通用Echarts组件
echarts 更强大,但也有一些不足:
1、是一个js库,不支持数据双向绑定
2、每次初始化图表时都要写一堆配置代码,尤其是当页面上有多个图表时,代码会显得非常冗余。
怎么办呢?
这时候,封装一个通用的 ECharts 组件就显得尤为重要了。
主要功能
1、封装复用、减少额外配置;2、大小自适应;3、数据双向绑定
2.1 Props 配置
组件通过 props
接收外部传入的配置项,主要包括:
renderType
:渲染模式,支持canvas
和svg
,默认是svg
。className
、id
、width
、height
:用于设置图表的容器样式。isAutoSize
:是否开启自适应功能,默认开启。options
:ECharts 的配置项,用于定义图表的具体内容和样式。
props: {
renderType: {
type: String,
default: 'svg'
},
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '100%'
},
isAutoSize: {
type: Boolean,
default: false
},
options: {
type: Object,
default: () => ({})
}
}
2.2 监听窗口大小变化
- 如果
isAutoSize
为true
,组件会监听resize
事件,并在窗口大小变化时调用resize()
方法。 - 使用
setTimeout
延迟执行,避免频繁触发resize
事件导致性能问题。
mounted() {
if (this.isAutoSize) {
window.addEventListener('resize', this.handleResize);
}
},
methods: {
handleResize() {
if (this.resizeHandler) clearTimeout(this.resizeHandler);
this.resizeHandler = setTimeout(() => {
this.chart()?.resize();
}, 200); // 延迟 200ms 执行
}
}
2.3 初始化 ECharts 实例
- 在
chart()
方法中,组件会检查当前 DOM 元素是否已经存在 ECharts 实例。如果存在,则直接复用;如果不存在,则初始化一个新的实例。 - 使用
$once
钩子确保在组件销毁时自动清理 ECharts 实例。
methods: {
chart() {
let chart = echarts.getInstanceByDom(this.$el); // 获取已存在的实例
if (!chart) {
chart = echarts.init(this.$el, null, { renderer: this.renderType }); // 初始化新实例
this.$once('hook:beforeDestroy', () => {
chart.clear();
if (chart) echarts.dispose(chart); // 销毁实例
});
}
return chart;
}
}
2.4 监听配置项变化
- 通过
watch
监听options
的变化,并在变化时调用setOption
方法更新图表。 deep: true
表示深度监听,确保嵌套对象的变化也能被捕获。
watch: {
options: {
handler(options) {
const chart = this.chart();
chart?.setOption(options, true); // 更新图表
},
deep: true
}
}
2.5 销毁清理
- 在组件销毁时,移除
resize
事件监听,并调用dispose()
方法销毁 ECharts 实例。 - 防止内存泄漏和额外的 CPU/GPU 占用。
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
}
2.6 全部代码
<template>
<div
:id="id"
:key="id"
:class="className"
:style="{ height: height, width: width }"
/>
</template>
<script>
import * as echarts from 'echarts'
export default {
name: 'Echart',
// 配置项
props: {
// 渲染器
renderType: {
type: String,
default: 'svg'
},
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '100%'
},
// 是否自适应
isAutoSize: {
type: Boolean,
default: false
},
options: {
type: Object,
default: () => ({})
}
},
data() {
return {
optionsData: null,
}
},
// 监听配置项变化,重新渲染echarts
watch: {
options: {
handler(options) {
const chart = this.chart()
chart?.setOption(options, true)
},
deep: true
}
},
mounted() {
// this.initChart()
// 监听窗口编号,自适应echarts
if (this.isAutoSize) { window.addEventListener('resize', this.handleResize) }
},
// 组件销毁的时候,移除事件监听
beforeDestroy() {
/* 页面组件销毁的时候,移除绑定的监听resize事件,否则的话,多渲染几次
容易导致内存泄漏和额外CPU或GPU占用哦*/
window.removeEventListener('resize', this.handleResize)
},
methods: {
// 监听窗口尺寸变化
handleResize() {
if (this.resizeHandler) clearTimeout(this.resizeHandler)
this.resizeHandler = setTimeout(() => {
// 获取当前元素的宽高
const width = this.$el.offsetWidth
// 获取当前元素的高度
const height = this.$el.offsetHeight
// 如果宽度不为空,则调用resize方法
if (width || height) {
this.chart()?.resize()
}
}, 200) // 延迟执行,防止频繁调用
},
// 创建实例
// 不要把chart实例赋值在this上。(this对象一直存在不会被回收【在vue中引用时】)
chart() {
let chart = this.$echarts.getInstanceByDom(this.$el) // 获取实例,不要重复初始化
if (!chart) {
chart = this.$echarts.init(this.$el, null, { renderer: this.renderType })
// $once钩子的好处:
// 1每个新的实例都程序化地在后期清理它自己
// 2减少dom查询
this.$once('hook:beforeDestroy', () => {
chart.clear()
if (chart) this.$echarts.dispose(chart)
})
}
return chart
}
}
}
</script>
<style></style>
通过这种方式,你可以在不同的页面中复用这个组件,只需要传入不同的 options
配置即可。代码简洁又优雅。
3. ECharts 开发思想:图层叠加
ECharts 的一个强大之处在于它的图层叠加能力。你可以通过叠加多个图层来实现一些非常酷炫的效果,比如地图的阴影、轮廓高亮、国界九段线等效果。
案例-地图轮廓高亮
举个例子,如下图是一个 中国地图边框发光效果。
实现思路
叠加俩个 geo
图层来实现。一个图层设置 边缘发光体,另一个图层作为地图背景,最终组合成一个复杂的地图。
在线预览
👉 轮廓高亮地图 + 九段线 - category-work,geo地理坐标,series-map地图,tooltip提示框 - makeapie echarts社区图表可视化案例
关键代码
option = {
geo: [
{
map: 'china',
roam: true,
itemStyle: {
areaColor: '#f4f4f4',
borderColor: '#ccc',
},
},
{
map: 'china',
roam: true,
itemStyle: {
areaColor: '#f4f4f4',
borderColor: '#ccc',
shadowColor: 'rgba(0, 0, 0, 0.5)',
shadowBlur: 10,
shadowOffsetX: 5,
shadowOffsetY: 5,
},
},
],
};
通过这种方式,你可以轻松实现一些复杂的视觉效果。
4. 常用配置项
ECharts 提供了丰富的配置项和组件,这里我们简单介绍几个常用的:
- series:图例合集,用于定义图表的数据系列。
- geo:地图相关配置,用于绘制地图。
- on() 和 off():用于绑定和取消事件。
5、富文本标签和悬浮提示
echarts 中有很多交互效果,比如Tooltip
鼠标悬浮提示、文本 hover效果呀,默认的样式一般,如何还原UI设计稿呢?咱们可以通过富文本标签方式,来自定义图表效果。
1. Tooltip 悬浮提示
功能
tooltip
是当用户与图表交互时(如鼠标悬停)显示的提示框,用于展示当前数据点的详细信息。
使用场景:
- 交互性:用户通过鼠标悬停或点击图表时,显示详细信息。
- 全局信息:通常展示与当前交互点相关的多个维度的数据。
- 临时性:仅在用户交互时显示,不常驻于图表中。
注意:
Tooltip 支持 自定义HTML 和富文本标签配置
示例:
tooltip: {
trigger: 'axis',
formatter: function (params) {
// 支持 返回 html 字符串模板
return `日期: ${params[0].name}<br>值: ${params[0].value}`;
}
}
2. Label 富文本标签
功能
label
是直接显示在图表元素(如柱状图的柱子、饼图的扇区等)上的文本标签,用于展示该元素的具体数值或其他信息。
注意:label 不支持自定义html,仅支持富文本 设置,详细查看 Documentation - Apache ECharts
使用场景
- 常驻信息:标签始终显示在图表元素上,用户无需交互即可看到。
- 局部信息:通常只展示当前元素的关键信息,如数值、百分比等。
- 美化图表:通过富文本样式增强图表的可读性和美观性。
示例
var uploadedDataURL = "/asset/get/s/data-1559121259198-sxyPSimU9.png";
series: [{
type: 'bar',
label: {
show: true,
formatter: '{b}: {c}',
rich: {
a:{
width:34,
height:33,
lineHeight: 50,
backgroundColor: {
image: uploadedDataURL
},
align: 'left'
},
},
b: {
color: 'red',
fontSize: 16
},
c: {
color: 'blue',
fontSize: 14
},
}
}
}]
3 .区别
特性 | Label | Tooltip |
显示方式 | 直接显示在图表上 | 悬浮时显示 |
内容 | 通常是简短的数值或名称 | 可以是详细的多行信息 |
交互性 | 静态,无需用户交互 | 动态,需要用户悬浮触发 |
适用场景 | 需要直观展示数据的场景 | 需要详细信息的场景 |
6. 图例设置
如下,带有icon缩略信息部分,为图表的图例组件
配置
基本配置、设置图例icon类型、大小、间距部分,同样也可以设置图例组件的整体的位置。
高级配置:如果自定义图例的字体类型、宽度、行高等信息,可以使用富文本标签来设置
代码
const colorList = ['#296DDF ', '#23D5FF', '#FFE18F', '#FFFFFF']
option = {
backgroundColor:'#000',
legend: {
// top:'0',
// selectedMode: false,
// 位置
left: '30%',
top: 'center',
//图例之间间距
itemGap: 15,
// 图例类型
icon: 'roundRect',// 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
// 图列宽度
itemWidth: 9,
itemHeight: 7,
// 图例文本
textStyle: {
color: '#77899c',
// 自定义标签富文本,课通过富文本设置字体类型、图片
rich: {
uname: {
width: 36,
color: '#D4DFFF',
fontSize: 12,
lineHeight: 18,
margin: 10,
fontFamily: "D-DIN,D-DIN",
},
unum: {
color: '#D4DFFF',
width: 44,
// height: 32,
// lineHeight: 22,
fontSize: 14,
// 文字内边距
// padding: [20, 54, 10, 0],
align: 'center',
fontFamily: "D-DIN,D-DIN",
}
}
},
// 根据宽度自适应,一行放置几个标签
width: 230,
formatter: (name) => {
let res = option.series[0].data.filter(v => v.name === name);
res = res[0];
return `{uname|${res.name}}{unum|${39}%}`
},
},
color: colorList,
series: [
{
name: '姓名',
type: 'pie',
// left:"-50px",
// radius 设置外环、内环大小
//默认单位是px,注意!如果是自适应页面,单位建议设置百分比,否则出现页面缩放后,图表未改变
radius: [ 80,90],
// 设置位置偏移
center: ['18%', '50%'],
label: {
// 正常下文字lable
normal: {
// 默认不显示
show: false,
position: 'center',
formatter: function (data) {
// console.log('data', data)
// 设置圆饼图中间文字排版
return `{value|${data.percent.toFixed(0)}%} \n{Sunny|${data.name}}`
},
// 富文本设置
rich: {
value: {
fontSize: 18,
lineHeight: 20,
align: 'center',
padding: [10, 0, 12, 10],
fontFamily: "D-DIN,D-DIN",
color: 'white'
},
Sunny: {
fontSize: 12,
lineHeight: 25,
fontFamily: 'AlibabaPuHuiTi_3_55_Regular',
align: 'center',
color: '#D4DFFF'
},
},
},
emphasis: {
show: true, //文字至于中间时,这里需为true
}
},
labelLine: {
show: true
},
itemStyle: {
borderWidth: 1,
borderColor: '#000'
},
data: [
{ name: '腾讯', value: 10 },
{ name: '百度', value: 100 },
{ name: '阿里', value: 120 },
{ name: '字节', value: 60 },
{ name: '快手', value: 120 }
],
}
]
}
通过以上这种方式,你可以轻松自定义图例的样式和内容。
7. 柱状图的高级配置
锥形柱子
如下图实现一个锥形柱效果
实现思路
使用 echarts 类型 pictorialBar
这是配置制象形柱状图,我们可以利用这个特性来实现锥形柱子的效果。
注意
柱状图顶部文案过多,可以进行缩放展示, 鼠标悬浮展示字体全称
代码
option = {
backgroundColor: '#031245',
// title: {
// text: '性能(ms)',
// textStyle: {
// color: '#fff',
// },
// },
grid: {
left: '10%',
top: '10%',
bottom: '10%',
right: '10%',
},
xAxis: {
// name: 'X',
nameTextStyle: {
color: '#FFFFFF',
padding: [0, 0, 0, 20],
},
show: true,
axisLine: {
show: true,
lineStyle: {
color: 'rgba(91,100,134,1)',
shadowColor: 'rgba(91,100,134,1)',
shadowOffsetX: '20',
},
symbol: ['none', 'arrow'],
symbolOffset: [0, 25],
},
splitLine: {
show: false,
lineStyle: {
color: 'rgba(255,255,255,0.2)',
},
},
axisLabel: {
show: true,
// rotate: -1,
fontSize: 12,
textStyle: {
fontSize: 25,
// fontFamily: PangMenZhengDao,
// fontWeight: 400,
color: '#CEF4FF',
lineHeight: 45,
},
},
axisTick: {
show: false,
},
data: ['驯鹿', '火箭', '飞机', '高铁', '轮船'],
},
yAxis: [
{
nameTextStyle: {
color: '#FFFFFF',
padding: [0, 0, 0, 20],
},
show: true,
axisTick: {
show: false,
},
axisLine: {
show: true,
symbol: ['none', 'arrow'],
symbolOffset: [0, 15],
lineStyle: {
// color: 'rgba(255, 129, 109, 0.1)',
width: 1, //这里是为了突出显示加上的
color: 'rgba(91,100,134,1)',
shadowColor: 'rgba(91,100,134,1)',
},
},
axisLabel: {
show: true,
// rotate: -1,
fontSize: 12,
textStyle: {
fontSize: 25,
// fontFamily: PangMenZhengDao,
// fontWeight: 400,
color: '#CEF4FF',
lineHeight: 45,
},
},
splitArea: {
areaStyle: {
color: 'rgba(255,255,255,.5)',
},
},
splitLine: {
show: true,
lineStyle: {
color: 'rgba(68,76,116,1)',
width: 1,
type: 'solid',
},
},
},
],
series: [
{
type: 'pictorialBar',
barCategoryGap: '5%',
// symbol: 'path://M0,10 L10,10 C5.5,10 5.5,5 5,0 C4.5,5 4.5,10 0,10 z',
symbol: 'path://M0,10 L10,10 C5.5,10 6.5,5 5,0 C3.5,5 4.5,10 0,10 z',
label: {
show: true,
position: 'top',
color: '#000',
fontSize: 14,
fontWeight: 'bold',
overflow: 'truncate', // 超出部分省略号显示
normal: {
offset: [-12, 0],
show: true,
position: 'top',
fontSize: 14,
color: '#4acf6f',
formatter: function (data) {
return data.name
},
},
// 鼠标选中显示全称
emphasis: {
show: true,
formatter: function(params) {
return params.data + ' - 详细信息'; // 鼠标悬浮时显示的文本
}
}
},
itemStyle: {
normal: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#9A11FF',
},
{
offset: 1,
color: '#08DFFE',
},
],
global: false, // 缺省为 false
},
},
emphasis: {
opacity: 1,
},
},
data: [{name:"海莲花",value:12}, 60, 25, 18, 12],
},
],
};
8. 饼状图的选中效果
在饼状图中,选中某个扇区时,通常会在中间显示该扇区的数据。这个效果可以通过配置 label
来实现。
const colorList = ['#47A2FF ', '#53C8D1', '#59CB74', '#FBD444', '#7F6AAD', '#585247']
option = {
legend: {
selectedMode:false,
animation: false,
// 位置
right: '10%',
top: 'center',
// 上下间距
itemGap: 10,
icon: 'roundRect',
// 图列宽度
itemWidth:15,
selected:true,
// 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none'
// 数据源
data: ['南京a', '南京b', '南京c', '南京d', '南京e'],
textStyle: {
color: '#77899c',
// 自定义标签富文本,课通过富文本设置字体类型、图片
rich: {
uname: {
width: 60
},
unum: {
color: '#4ed139',
width: 60,
align: 'center'
}
}
},
// 根据宽度自适应,一行放置几个标签
width:300,
formatter(name) {
return `{uname|${name}}{unum|1132}`
},
},
color: colorList,
series: [
{
name: '姓名',
type: 'pie',
selectedMode: 'single',
selectedIndex: 1, // 这里设置默认选中第3个扇区,索引从0开始
radius: [60, 90],
center: ['40%', '50%'],
label: {
normal: {
show: false,
position: 'center',
formatter: function(data){ // 设置圆饼图中间文字排版
return data.value+"\n"+"{value|这段文本采用样式value}"
},
rich: {
value: {
lineHeight: 20,
align: 'center',
color:'red'
},
Sunny: {
height: 10,
align: 'center',
color:'red'
},
},
},
emphasis: {
show: true, //文字至于中间时,这里需为true
textStyle: { //设置文字样式
fontSize: '32',
color:"#333"
},
}
},
labelLine: {
show: false
},
itemStyle: {
borderWidth: 3,
borderColor: '#fff'
},
data: [
{name: '南京a', value: 10},
{name: '南京b', value: 100},
{name: '南京c', value: 100},
{name: '南京d', value: 60},
{name: '南京e', value: 100},
],
}
]
};
setTimeout(()=>{
myChart.dispatchAction({type: 'highlight',seriesIndex: 0,dataIndex: 0});
// 当鼠标移入时,如果不是第一项,则把当前项置为选中,如果是第一项,则设置第一项为当前项
myChart.on('mouseover',function(e){
myChart.dispatchAction({type: 'downplay',seriesIndex: 0,dataIndex:0});
if(e.dataIndex != index){
myChart.dispatchAction({type: 'downplay', seriesIndex: 0, dataIndex: index });
}
if(e.dataIndex == 0){
myChart.dispatchAction({type: 'highlight',seriesIndex: 0,dataIndex:e.dataIndex});
}
});
//当鼠标离开时,把当前项置为选中
myChart.on('mouseout',function(e){
index = e.dataIndex;
myChart.dispatchAction({type: 'highlight',seriesIndex: 0,dataIndex: e.dataIndex});
});
},10)
9 . 其他
1、获取地图数据
在使用 ECharts 绘制地图时,通常需要获取地图的 JSON 数据。你可以通过 Datav 或者高德地图的 API 来获取这些数据。
DataV.GeoAtlas地理小工具系列
总结
ECharts 是一个非常强大的数据可视化工具,通过合理的配置和封装,你可以实现各种复杂的图表效果。希望这篇文章能帮助你更好地理解和使用 ECharts。如果你有任何问题或建议,欢迎在评论区留言讨论!
好了,今天的分享就到这里,我们下次再见!