当前位置: 首页 > article >正文

ECharts 实战指南:组件封装+地图轮廓高亮 + 自定义 Tooltip+轮播+锥形柱子

大家好,我是一诺。今天我们将深入探讨 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:渲染模式,支持 canvassvg,默认是 svg
  • classNameidwidthheight:用于设置图表的容器样式。
  • 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 监听窗口大小变化

  • 如果 isAutoSizetrue,组件会监听 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。如果你有任何问题或建议,欢迎在评论区留言讨论!

好了,今天的分享就到这里,我们下次再见!

 


http://www.kler.cn/a/548867.html

相关文章:

  • 【MODIS\VIIRS】h5文件信息查看python代码
  • Pytorch深度学习教程_3_初识pytorch
  • 基于AWS云平台的法律AI应用系统开发方案
  • 【iOS】包大小和性能稳定性优化
  • 如何测试和验证CVE-2024-1430:Netgear R7000 路由器信息泄露漏洞分析
  • 详细介绍:封装简易的 Axios 函数获取省份列表
  • 【云安全】云原生-K8S(四)安全问题分析
  • 【前端】自己从头实现一个gpt聊天页面
  • 用命令模式设计一个JSBridge用于JavaScript与Android交互通信
  • Visionpro 齿轮测量
  • DeepSeek-R1使用生存指南
  • Windows逆向工程入门之汇编位运算
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cycle_t 类型
  • DeepSeek辅助测试测试一 -- DeepSeek加MaxKB知识库本地部署
  • 如何通过AI轻松制作PPT?让PPT一键生成变得简单又高效
  • 一键终结环境配置难题:ServBay 1.9 革新 AI 模型本地部署体验
  • HTML5 起步
  • 「AI学习笔记」机器学习与深度学习的区别:从技术到产品的深度解析(四)...
  • IoTDB 节点宕机后集群恢复
  • 轻量级分组加密算法RECTANGLE