大屏图表,ECharts 从“熟练”到入门

39,243 阅读12分钟

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!

📖阅读本文,你将

  1. 了解 配置驱动 的思想
  2. 理解 Echarts 基本概念
  3. 了解 graphic动画基本玩法
  4. 了解 Echarts 基底组件的封装的思路

一、不是标题党!Echarts,简历上人均 “熟练”?

公司最近在招外包,而因为目前大屏的需求比较多,所以我就稍微关注了一下候选人们简历上对于 Echarts 的熟悉程度。

不看不知道,一看下一跳,Echarts 属实已经是业内人均 “熟练” 的基本技能了。

以上是短短几篇简历中摘选的自我描述,可以看出,Echarts 在大众研发的心中,已经是和 AntdElementUIBootstrap 相当的常用第三方库了。

所以,我就开始在面试中加入了关于 Echarts 使用的相关问题,想看看 熟练 到底是什么意思。

但是一轮问题问下来,大家好像又对它很陌生。

难道,所谓 熟练,就是能在社区里找到 Demo,然后把配置项 Copy 到项目之中?

问:“现在你找到 Demo 了,但是 Demo 没有显示图例(也就是那些彩色小方块小圆圈,表示各类数据颜色的东西),你应该查文档的什么关键词?”

答:“一时说不出来,但我肯定能试出来...”

问:“我想把UI出的一张图片放在饼图的背面,作为背景,应该怎么弄?”

答:“先去社区找找 Demo 吧...”

问:“这种 Demo 可能不太好找吧...而且有些场景,Demo 涉及的也不多,比如需要在某个特定位置放一些字,做一些动画,应该怎么办?”

答:……

虽然我的习惯和大家是一样的,遇到需求,先去社区里找一个相似度最高的 Demo 再说,但是开发大屏,怎么可能不遇到 “特异化、定制化” 的诉求呢?

Demo 帮不了你的时候,你是否可以熟练地通过文档,找到合适的解决之道呢?

如果 找DEMO 就是 熟练,那我建议咱还是先忘记 “万事求DEMO” 的这种熟练方式,重头捋一捋 Echarts 这个框架,先力求 入门

这就是标题所谓的,从 熟练入门 的意思。

二、大屏图表,为什么我选 Echarts?

在问出这个问题之前,我觉得还是先问一问:为什么不选Echarts 呢?

大屏的本质是什么?是讲故事,是给客户 量身定做 的讲故事!因此,大屏需求,更多的情况就是 做项目,而不是 做产品

做项目,什么最重要?

六个字:成本低、效率高

老板:你说复用、优雅?我都觉得好笑。

  • 成本低
    软件项目最大的成本,就是人的成本,现在哪怕是培训班刚出来的小萌新,哪怕他压根不知道 g2plot 是什么,但简历上大概率会写上 熟练使用 Echarts。如果招到那种五六年的前端老兵,那 EchartsAPI 熟练度只会更高。

    花更低的钱,能招来直接做项目的人,这就是成本低。

  • 效率高
    为什么 Echarts 效率更高,因为它的社区实在太成熟,各种样式风格 Demo 的沉淀实在太多。 比如:早期的 Echarts gallery (已被关闭)、又比如:www.isqqw.com/

    在这些社区里,你可以快速找到各类你想要的风格和实现,从而低成本改造成 UI 想要的样子。

Echarts 通常被拿来和 AntV 进行横向对比,关于孰优孰劣,某乎上的争论也比较大,但大家似乎都能达成一种共识:

社区方面,Echarts 完胜。

社区完胜,效率就完胜,这是很容易推导的结果;毕竟,大多数时候,大家的开发模式就是 社区找相似度高的DEMO

另外,Echarts 早已经是 Apache 基金会在托管了,没有面向 KPI 的压力。(你懂我的意思吧?)

三、认识 Echarts

一个基于 JavaScript 的开源可视化图表库。 ——官网

一个看似什么都说了,却什么都没说的客观发言。

但是作为开发者,我们却需要通过阅读文档,自行去摸索一些更为核心的内容,以了解:Echarts 能做什么? Echarts 擅长做什么? Echarts 有哪些特性? ”

  • 它能开发图表、地图、做图形渲染
  • 它擅长做图表开发

至于特性?

我虽然也只是 Echarts 入门使用者,我冒昧而经验地认为它的三个核心特性是:

  • 1、配置驱动
  • 2、事件驱动
  • 3、图形能力

理解以上三点,我认为大概就能做到 “入门Echarts” 这个常用前端工具了。但本文还是会把重点放在 配置驱动 这个最为核心的能力讲解上。

3.1 什么是配置驱动

什么是 配置驱动

这其实是很好理解的一个定义:

我只需要修改配置,就能让它显示的画面完成更新。

在使用 Echarts 的过程中,最重要的 API 一定是 Echarts.setOption

伪代码如下:

// 让图表显示状态A
chart.setOption(optionA)
// 让图表更新状态到B
chart.setOption(optionB)

对,这就是 配置驱动

稍微改动官方示例:

<template>
  <div class="chart" ref="chartEl"></div>
</template>

<script setup>

const chartEl = ref(null);
let chart = null
const categories = ['老包', '老南', '老君']
const genDataArr = () => {
  return categories.map(t => {
    return Math.ceil(Math.random() * 100)
  })
}
onMounted(() => {
  // 指定图表的配置项和数据
  const chart = echarts.init(chartEl.value);
  const option = {
    tooltip: {},
    legend: {
      data: ['黑子程度']
    },
    xAxis: {
      data: categories
    },
    yAxis: {},
    series: [
      {
        name: '销量',
        type: 'bar',
        data: genDataArr()
      }
    ]
  };
  chart.setOption(option)

  setInterval(() => {
    chart.setOption({ series: [
      {
        data: genDataArr()
      }
    ] })
  }, 1000)
})
</script>

通过修改配置项中某一项的值,我们可以清晰看到呈现出的动态变化:

如果你无法看到上述 码上掘金 内容的变化,那有可能是因为网络原因,无妨,其 gif 效果如下:

配置驱动 就是这么直观,且易于理解。

它最大的优点就在于:

当我们希望图表从"状态A"进化到"状态B"时,我们其实无需关注当前的状态到底是什么样,我们只需要组装好"状态B"的配置,并将它交付给 Echarts 即可。

基本上,你可以利用 配置项 完成绝大多数工作上你遇到的各种需求。鲜明而无状态的 API 设计,也是它如此受到大家喜爱的根本原因。

本文,我们先专注于 配置驱动 封装一个能应对前端 90% 业务开发场景的基础 Vue3 基底组件。

3.2 配置项基本概念

配置项固然简单,但你首先需要知道 “我要实现某个能力实,应该去寻找什么样的配置” 。

对此,最快去了解各种概念的办法是看官方文档的 常用组件说明

如果你觉得看文档不如看图片,我也单独整理了一份简图:

通过图中相关对照,我们可以在官方文档的配置项手册 中快速浏览相关内容的支持选项及用法。

3.3 轻度自定义的神器:graphic

以上配置项中,大多数能力都是 Echarts 预设好,我们直接拿来用” 模式的,但难免会遇到产品经理或者 UI 同学突发奇想,增加一些特殊的文本或者图案,比如:

这种就属于,乍一眼看上去很容易,然后一琢磨:不对啊,DEMO 里找不到可以直接拿来用 的类型了。

怎么办?

'graphic' 可能是个不错的选择。

graphic 配置提供了以下节点能力的渲染配置:

  • 图片
  • 文本
  • 元素组(可包含子节点,对子节点进行整体缩放、旋转等)
  • 以及 其他各种图形,例如(圆形、扇形、环形、多边形、折线、矩形、直线、贝塞尔曲线、圆弧)

其中,我认为最常用的两种:

  • 图片
  • 文本

让我们先通过一个例子认识 图片文本 的用法,核心代码如下:

option = {
  ...// 省略其他,
  graphic: [
      {
        type: 'image',
        style: {
          image: 'https://pic.zhangshichun.top/pic/circle.png',
          width: 150,
          height: 150
        },
        top: 'middle',
        left: 'center',
      },
      {
        type: 'text',
        style: {
          x: 100,
          y: 150,
          text: `鸡你太美`,
          fill: '#fff',
          stroke: '#fff',
          textAlign: 'center',
          fontSize: 14
        }
      },
      {
        type: 'text',
        style: {
          x: 100,
          y: 180,
          text: `唱跳rap`,
          fill: '#fff',
          stroke: '#fff',
          textAlign: 'center',
          fontSize: 14
        }
      },
  ]
}

码上掘金相关片段,赶紧亲自试试:

效果图:

3.4 配置化与动画

细心的同学想必已经发现了,我并没有声明和动画相关的东西,但我上面的示例里已经有了最基本的 补间动画,这是因为 echarts 内置了一部分默认动画逻辑。

但实际上,你也拥有配置动画的能力

  1. 关键帧效果,以及循环动画

    尝试在 type: 'image' 的元素上添加以下代码:

    {
      // ... 其他配置暂且省略
      transition: 'rotation',
      originX: 75,
      originY: 75,
      keyframeAnimation: {
        duration: 3000,
        loop: true,
        keyframes: [{
            percent: 0.5,
            easing: 'linear',
            rotation: Math.PI
        }, {
            percent: 1,
            easing: 'linear',
            rotation: Math.PI * 2
        }]
      }
    }
    

    而你会看到如下效果: 鸡哥的篮球 背景的圆环永远地滚动了起来。

    这个效果正是借助了 Echarts 提供的关键帧动画能力,指定时长、以及不同进度上元素应该表现的配置状态,驱使它完成了页面的渲染。

  2. 进入/离开/更新动画

    关键帧动画更倾向于按照 编码者指定的方式 进行运转,而 “进入/离开/更新动画” 则适用于 更新数据状态时,自动获取动画 的能力。
    看代码:

    // 移除上面 keyframeAnimation 这一段代码
    // 增加以下代码:
    setInterval(() => {
      const rotation = Math.random() * 2 * Math.PI
      chart.setOption({
        graphic: [
          {
            rotation
          }
        ]
      })
    }, 500)
    

    效果如下: 鸡哥的篮球 背景的圆环 rap 起来了。

学会以上动画能力,也基本算可以称得上在 Echarts 使用上入门了,可以应对大多数 UI 和产品的奇思妙想了。

四、 基底组件的能力?

我对 基底组件 的期望,其实很简单:

  • 它能自适应容器的宽高,并让自身的图表自适应容器宽高的变化

    这当然是痛点了,我不止一次,不止在一个项目里,发现大屏进行布局调整、或者是浏览器页面大小调整后,图表的大小变得不适宜。依靠研发在紧张工期下,依然保持严谨的防御性代码编写习惯,是一种奢望。

  • 它能具备基础的主题能力

    大屏通常有自己的主题色,自带 “主题色” 的 Echarts 组件,可以让研发节省大量时间。

  • 当数据为空时,它能智能的显示 “空值缺省”

    试想,哪个客户希望对着一个空空如也的折线图猜测用意呢?此时,一句简简单单的 暂无数据 是多么的贴心。

  • 它应该给我最大限度的 自定义 的能力

    永远不要失去能力,封装的意义在于 增强,而不是 阉割

基于以上诉求,我们可以开始设计自己的 Echarts 基底组件 的API。

src/components/BaseECharts/index.vue

// 提供 props 属性 empty,让组件稳定感知当前是否是空数据
defineProps({
  empty: {
    type: Boolean,
    default: false
  }
})

// 向外弹射事件 'resize'
const emits = defineEmits(['resize'])

defineExpose(
  {
    // 向父组件暴露方法:获得ECharts实例
    getChart: () => {
    },
    // 向父组件暴露方法:更新ECharts配置
    setOption: (v) => {
    }
  }
)

五、如何实现动态适配容器宽高

先了解一个 API: ResizeObserver。 (MDN相关参考)

通过 ResizeObserver,我们可以从容地监听某个元素的尺寸变化。

既然这个 API 这么优秀,那我们直接用它?你们可以随便用,但本老年 vue-coder 选择逻辑完备的 Hooks: useElementSize,一行代码解决元素尺寸监听的问题:

const el = ref(null)
const { width, height } = useElementSize(el) // width和 height 都是响应式的 `ref` 对象

然后再监听相关变化,适时地调用 chart.resize() 方法即可:


watchEffect(() => {
  if (width.value && height.value) {
    emits('resize', { width: width.value, height: height.value })
    chartRef.value?.resize();
  }
})

这样,当视窗大小变化后,相关的图表也会重新根据新视窗的大小调整尺寸。

而在 @resize 事件里,用户因为得到了 {width, height},对于一些依赖绝对尺寸的场景,也可以从容更新 option,保证 resize 后尺寸不会出现问题。

比如,在父组件中,我们可以这样写:

<template>
  <BaseECharts @resize="size => onResize(size)" ref="chartRef"></BaseECharts>
</template>
<script setup>
import BaseECharts from '@/components/BaseECharts'

const chartRef = ref(null)
const genOption = ({ width, height }) => {
  const minSideLength = Math.min(width, height)
  return {
    series: [
      // ...其他选项省略
      {
        radius: [0.3 * minSideLength, 0.4 * minSideLength],
        center: [minSideLength / 2, minSideLength / 2],
        type: 'pie',
      }
    ]
  }
}
const onResize = (size) => {
  const option = genOption(size)
  chartRef.value.setOption(option)
}
</script>

从而实现 “像素级” 的尺寸操控(当然,能用百分比解决的就用百分比,但某些场景不支持百分比,那就必须上像素了);

六、如何具备内置的主题色?

首先,ECharts 是支持自定义主题的,访问: echarts.apache.org/zh/theme-bu…

可以通过左侧的操作栏,让 UI同学快速定制一套适合你们系统的主题色,然后导出主题 json 配置文件,并将它存在 '@/theme/echarts-dark.json' 文件中。

src/main.js 中写如下带代码:

import * as ECharts from "echarts";
import theme from '@/theme/echarts-dark.json'

ECharts.registerTheme('dark', theme)

完成对主题的注册,(写在 main.js 是为了避免重复注册),当然,如果要充分组件化的话,此处应该有 vue-plugin

然后,在基底组件中如此写:

onMounted(() => {
  const chart = Echarts.init(chartEl.value, 'dark');
})

完成对主题的初始化。

七、空值缺省

这一步,其实不太涉及到 Echarts 本身的内容,只需要通过判断 empty 属性,通过 v-show 切换展示内容。

为什么用 v-show 不用 v-if ? 为了降低 echarts 实例周期管理的复杂度。

src/components/BaseECharts/index.vue

<template>
  <div class="chart">
    <div v-show="!empty" class="chart__el" ref="chartEl"></div>
    <NoData v-show="empty" class="chart__empty"></NoData>
  </div>
  
</template>

八、总结

通过:

  • 学习配置驱动的思想
  • 理解 Echarts 基本概念
  • 学习 graphic动画基本玩法
  • 完成基底组件的封装

你已经可以胜任简单大屏项目图表的快速开发和迭代了,此时,如果在简历上写上 入门 Echarts 使用,应该就不会再出现尴尬的场景了吧。

面对面试官高深场景的问题,也可以理直气壮地回答一句:

我就入门水平,你还想怎样?