• echarts学习:基本使用和组件封装


    前言

    我在工作中使用echarts较少,这就导致每次使用时都要从头再来,这让我很头疼。因此我决心编写一系列文章将我参与工作后几次使用echarts所用到的知识记录下来,以便将来可以快速查阅。

    一、基本使用

    像我一样的新手,想要入门echarts还是建议要从官方文档的快速上手开始。我在这里会记录其中的几个简要的步骤。

    1.安装

    NPM

    npm install echarts

    CDN

    <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js">script>

    2.项目中引入

    注意最新的版本一定要用这种带有as的方式,某些老的博客文章中可能写的是旧版的引入方式。

    import * as echarts from 'echarts';

    3.绘制图表

    使用echarts绘制图表很简单,我把它称为“echarts的三步走战略”:准备容器获取实例设置选项

    3.1准备容器

    准备容器就是要准备一个element作为放置echarts图表的容器,许多的图表库都有这样的操作。容器元素的作用就是确定图表在我们应用中的位置,以及限制图表的范围,因此容器必须要设置尺寸样式(是谁忘了设置尺寸?哦,原来是我 (*/ω\*) ,那没事了)。

    1. <div id="main" style="width: 600px;height:400px;">div>

    还有一个需要注意的点是,如果在Vue中使用echarts,有时可能会出现无法获取到dom的情况。想要了解具体的问题情境和解决方法请浏览我的这篇文章:vue3中获取元素DOM的方法

    3.2 获取实例

    获取实例就是通过echarts.init()工厂方法获取一个echarts实例,init方法接收一个dom(容器dom)返回一个实例。

    1. <template>
    2. <div ref="container" style="width: 600px; height: 400px">div>
    3. template>
    4. <script setup>
    5. import { onMounted, ref } from 'vue'
    6. import * as echarts from 'echarts'
    7. // 容器元素
    8. const container = ref(null)
    9. onMounted(() => {
    10. // 获取实例
    11. const chartInstance = echarts.init(container.value)
    12. })
    13. script>

    3.3 设置选项

    最后需要使用echarts实例的setoption方法设置配置项。配置项是echarts的核心,其内容博大精深,想了解更多有关echarts配置项的信息,请查阅echarts配置项手册

    1. <template>
    2. <div ref="container" style="width: 600px; height: 400px">div>
    3. template>
    4. <script setup>
    5. import { onMounted, ref } from 'vue'
    6. import * as echarts from 'echarts'
    7. // 容器元素
    8. const container = ref(null)
    9. onMounted(() => {
    10. // 获取实例
    11. const chartInstance = echarts.init(container.value)
    12. // 设置选项
    13. chartInstance.setOption({
    14. xAxis: {
    15. type: 'category',
    16. show: false,
    17. boundaryGap: false,
    18. },
    19. yAxis: {
    20. show: false,
    21. },
    22. series: [
    23. {
    24. type: 'line',
    25. data: [
    26. 620, 432, 220, 534, 790, 430, 220, 320, 532, 320, 834, 690, 530, 220,
    27. 620,
    28. ],
    29. areaStyle: {
    30. color: 'purple',
    31. },
    32. lineStyle: {
    33. width: 0,
    34. },
    35. itemStyle: {
    36. opacity: 0,
    37. },
    38. smooth: true,
    39. },
    40. ],
    41. grid: {
    42. top: 0,
    43. bottom: 0,
    44. left: 0,
    45. right: 0,
    46. },
    47. })
    48. })
    49. script>

    最后绘出如下的图表

    二、封装echarts组件

    大概在一年前我在公司的某个项目中看到了封装的echarts组件,之后我也曾经研究过,但最后都半途而废了。如今我重头再来,希望这次可以成功。

    1.为什么要封装echarts组件?

    echarts组件的主要作用是对上面所提到的 "三步走战略" 进行一个封装,这样我们就不需要重复去执行这些步骤了。每次绘图时只需要给echarts组件传递配置项即可。

    2.echarts组件需要实现的功能

    我封装的echarts组件准备实现以下的功能:

    基础功能

    优化功能

    拓展功能

    1. 基本的“三步走战略”
    2. 销毁实例
    3. 图表的重绘
    1. 图表尺寸的自适应(当页面大小变化时,图表尺寸跟着变化)
    2. 图表宽高可设置
    3. 展示loading动画
    1. 侦听图表的点击事件(这里也可以补充其它需要的事件)
    2. 暴露实例方法

    3.封装基本功能

    再复习一下,使用echarts绘制图表的“三步走战略”是:

    1. 准备容器
    2. 获取实例
    3. 设置选项
    1. <template>
    2. <div ref="container" style="width: 600px; height: 400px">div>
    3. template>
    4. <script setup>
    5. import { onMounted, onUnmounted, ref ,nextTick} from 'vue'
    6. import * as echarts from 'echarts'
    7. // props
    8. const props = defineProps({
    9. option: {
    10. type: [Object, null],
    11. require: true,
    12. }, //配置项
    13. })
    14. // 容器元素
    15. const container = ref(null)
    16. // 实例
    17. const instance = ref(null)
    18. // 初始化
    19. function init() {
    20. if (!container.value) return
    21. //查看容器上是否已经挂载了echarts实例 , 如果已挂载就获取并使用该实例,如果未挂载就进行初始化
    22. instance.value = echarts.getInstanceByDom(container.value)
    23. if (!instance.value) {
    24. instance.value = echarts.init(container.value)
    25. }
    26. draw()
    27. }
    28. // 绘制图表
    29. function draw() {
    30. if (!props.option || !instance.value) return
    31. instance.value.setOption(props.option)
    32. }
    33. onMounted(() => {
    34. nextTick(() => {
    35. init()
    36. })
    37. })
    38. script>

    考虑容器已被挂载echarts实例的情况

    上面的代码中值得注意的一个点是,在初始化的时候考虑了容器可能已被挂载了实例的情况。

    为了兼容这种情况就首先使用echarts.getInstanceByDom获取容器上的实例。如果容器已有实例就使用该实例,如果容器上没有实例就进行初始化。

    1. instance.value = echarts.getInstanceByDom(container.value)
    2. if (!instance.value) {
    3. instance.value = echarts.init(container.value)
    4. }

    4.将echats实例代理为响应式对象可能带来的风险

    将echarts实例保存为响应式对象,可能会造成一系列意外的问题。因此在这里建议将echarts组件中的实例使用普通变量或者shallowRefshallowReactivemarkRaw等API进行保存。

    我是使用了shallowRef来取代原来的ref:

    1. // 实例
    2. const instance = shallowRef(null)

    想要了解更多的相关信息,可以浏览我写的这篇文章 将echats实例代理为响应式对象可能带来的风险

    5.销毁实例

    当echarts组件被卸载时,我们应当调用dispose方法销毁实例,以防止可能得内存泄漏。

    1. onUnmounted(()=>{
    2. instance.value?.dispose()
    3. })

    6.图表重绘

    我们希望当echarts组件中接收到的一些props(例如option)发生变化时可以重新绘制图表。

    我一开始的写法是这样的

    1. function draw() {
    2. if (!props.option || !instance.value) return
    3. // 先清空实例
    4. instance.value && instance.value.clear()
    5. // 然后再重新绘制
    6. instance.value.setOption(props.option)
    7. }
    8. watch(props,()=>{
    9. draw()
    10. })

    后来发现只需要在调用setOption方法时将其notMerge选项设为true,就会自动的先清空组件然后再根据传入的配置项渲染新的图表。

    1. function draw() {
    2. if (!props.option || !instance.value) return
    3. instance.value.setOption(props.option, {
    4. notMerge: true,
    5. })
    6. }
    7. watch(props,()=>{
    8. draw()
    9. })

    7.实现图表的自适应

    我们希望当页面的尺寸发生变化时,图表的尺寸也会跟着变化。我实现这一功能的基本思路如下图所示:

    我使用ResizeObserver监听容器元素的尺寸变化,当容器的尺寸变化时,调用chartInstance.resize方法重置图表尺寸。想了解与图表自适应相关的详细内容,请参考我的文章:图表自适应

    1. const resizeObserver = shallowRef(null) // 元素尺寸侦听器
    2. const debounceTimer = ref(null) //防抖计时器id
    3. // 重置图表尺寸
    4. function resize() {
    5. clearTimeout(debounceTimer.value)
    6. debounceTimer.value = setTimeout(() => {
    7. instance.value?.resize({
    8. animation: {
    9. duration: 300,
    10. },
    11. })
    12. debounceTimer.value = null
    13. }, 300)
    14. }
    15. resizeObserver.value = new ResizeObserver(resize)
    16. onMounted(() => {
    17. nextTick(() => {
    18. resizeObserver.value.observe(container.value)
    19. })
    20. })
    21. onUnmounted(() => {
    22. resizeObserver.value?.disconnect()
    23. resizeObserver.value = null
    24. clearTimeout(debounceTimer.value)
    25. debounceTimer.value = null
    26. })

    8.图表宽高可设置

    我们希望可以在使用echarts组件的时候自定义图表的宽高。想要实现这一功能也很简单,只需要增加两个prop即可。

    1. const props = defineProps({
    2. width: {
    3. type: String,
    4. default: '300px',
    5. },
    6. height: {
    7. type: String,
    8. default: '200px',
    9. },
    10. })
    <div ref="container" :style="{width: props.width, height: props.height}">div>

    9.显示loading动画

    很多时候图表数据都是异步的,获取数据需要时间,此时就希望在数据获取的时候给用户展现loading动画效果,以提升用户体验。

    以前我都是借助于第三方组件的的loading组件来实现这一功能的(例如Ant Design 的 或者 Element-ui 的 v-loading)。

    现在才知道其实echarts是有自带的loading动画效果的,可以通过实例的showLoadinghideLoading方法控制。现在我们就可以借助这两个方法来实现图表的loading效果:

    1. const props = defineProps({
    2. loading: {
    3. type: Boolean,
    4. default: false,
    5. },
    6. })
    7. // loading动画
    8. watchEffect(() => {
    9. props.loading ? instance.value?.showLoading() : instance.value?.hideLoading()
    10. })

    让我们来看一下最终的效果:

    10.侦听图表的事件

    echarts图表有一套自己的事件系统,当我们需要实现一些与图表的交互效果的时候,可能就需要用到这些事件。我们可以在echarts组件中提前封装这些事件以便在将来使用。例如我添加一个图表的点击事件:

    1. const props = defineProps({
    2. // 省略...
    3. onClick: {
    4. type: Function,
    5. },
    6. })
    7. function init() {
    8. // 省略...
    9. addListeningChartEvent()
    10. }
    11. // echarts事件绑定
    12. function addListeningChartEvent() {
    13. // 点击事件
    14. props.onClick &&
    15. instance.value?.on('click','series', (e) => {
    16. props.onClick(e, instance.value, props.option)
    17. })
    18. }

    11.暴露echarts实例

    上面提到了在组件中侦听图表事件,但授人以鱼不如授人以渔,比起琐碎的封装事件,不如直接将echarts实例暴露给使用者。

    我们可以参考Vue官方文档了解如何从组件中暴露属性和方法:

    于是我使用defineExpose方法在将我组件中的实例暴露出来:

    1. defineExpose({
    2. getInstance: () => instance.value,
    3. resize,
    4. draw,
    5. })

    12.完整代码

    1. <template>
    2. <div
    3. ref="container"
    4. :class="props.className"
    5. :style="{ width: props.width, height: props.height }"
    6. >div>
    7. template>
    8. <script setup>
    9. import {
    10. nextTick,
    11. onMounted,
    12. onUnmounted,
    13. ref,
    14. shallowRef,
    15. watch,
    16. watchEffect,
    17. } from 'vue'
    18. import * as echarts from 'echarts'
    19. // props
    20. const props = defineProps({
    21. option: {
    22. type: [Object, null],
    23. require: true,
    24. }, //配置项
    25. width: {
    26. type: String,
    27. default: '300px',
    28. },
    29. height: {
    30. type: String,
    31. default: '200px',
    32. },
    33. className: {
    34. type: String,
    35. default: 'cl-chartCom',
    36. },
    37. loading: {
    38. type: Boolean,
    39. default: false,
    40. },
    41. onClick: {
    42. type: Function,
    43. },
    44. })
    45. const container = ref(null) // 容器元素
    46. const instance = shallowRef(null) // 实例
    47. const resizeObserver = shallowRef(null) // 元素尺寸侦听器
    48. const debounceTimer = ref(null) //防抖计时器id
    49. // 初始化
    50. function init() {
    51. if (!container.value) return
    52. //查看容器上是否已经挂载了echarts实例 , 如果已挂载就获取并使用该实例,如果未挂载就进行初始化
    53. instance.value = echarts.getInstanceByDom(container.value)
    54. if (!instance.value) {
    55. instance.value = echarts.init(container.value)
    56. }
    57. // 绘制图表
    58. draw()
    59. // 侦听图表事件
    60. addListeningChartEvent()
    61. }
    62. // 绘制图表
    63. function draw() {
    64. if (!props.option || !instance.value) return
    65. instance.value.setOption(props.option, {
    66. notMerge: true,
    67. })
    68. }
    69. // 图表自适应
    70. // 重置图表尺寸
    71. function resize() {
    72. clearTimeout(debounceTimer.value)
    73. debounceTimer.value = setTimeout(() => {
    74. instance.value?.resize({
    75. animation: {
    76. duration: 300,
    77. },
    78. })
    79. debounceTimer.value = null
    80. }, 300)
    81. }
    82. resizeObserver.value = new ResizeObserver(resize)
    83. // 重绘图表
    84. watch(props, () => {
    85. nextTick(() => {
    86. draw()
    87. })
    88. })
    89. // loading动画
    90. watchEffect(() => {
    91. props.loading ? instance.value?.showLoading() : instance.value?.hideLoading()
    92. })
    93. // echarts事件绑定
    94. function addListeningChartEvent() {
    95. // 点击事件
    96. props.onClick &&
    97. instance.value?.on('click', 'series', (e) => {
    98. props.onClick(e, instance.value, props.option)
    99. })
    100. }
    101. onMounted(() => {
    102. nextTick(() => {
    103. init()
    104. resizeObserver.value.observe(container.value)
    105. })
    106. })
    107. onUnmounted(() => {
    108. instance.value?.dispose()
    109. resizeObserver.value?.disconnect()
    110. resizeObserver.value = null
    111. clearTimeout(debounceTimer.value)
    112. debounceTimer.value = null
    113. })
    114. defineExpose({
    115. getInstance: () => instance.value,
    116. resize,
    117. draw,
    118. })
    119. script>

    参考资料

    1. Vue3 封装 ECharts 通用组件_vue3如何封装echarts-CSDN博客
    2. Vue3 封装ECharts 组件 抽离复用 包含图表随着窗口宽高变化而变化-CSDN博客
    3. echartsInstance.setOption - Apache ECharts
    4. echartsInstance.showLoading - Apache ECharts
    5. events.鼠标事件.click - Apache ECharts
    6. 模板引用-Vue.js

  • 相关阅读:
    Nginx简介与Docker Compose部署指南
    003-CSS-斑马线&棋盘效果
    【C语言 |预处理指令】预处理指令详解(包括编译与链接)
    超800万辆「巨量」市场,谁在进攻智能驾驶「普及型」赛道
    python-数据描述与分析(1)
    找准边界,吃定安全 | 高性能硬件防御问题难解?硬件加速引擎闪亮登场
    ConvNeXt(CVPR 2022)论文解读
    bash调试方法总结
    阿里云国际版邮件服务套餐购买流程
    基于线性表的图书信息管理系统
  • 原文地址:https://blog.csdn.net/m0_52912501/article/details/139373916