如何使用 formatter 回调函数实现 ECharts 弹窗

691 阅读2分钟

前言

在项目中,常常会遇到 ECharts 弹窗的需求。有些很简单,有些就比较刁钻了,非要你搞点花样来,才显得高级。最常见的是地图弹窗,但是今天不想举地图弹窗的例子,就拿简单的柱状图来举例吧,比较省事。

本文涉及到的 MyChart 组件请见我的上一篇文章 基于 ECharts 的二次封装

一、内置原始弹窗 tooltip

首先来一个小 Demo,实现弹窗效果,我们只需要在配置项里面设置 tooltip: { show : true } 即可。这么个小动作,就可以解决一般的业务需求。

1.代码落地

<template>
  <div class="echartBar">
    <MyChart :op="op" :height="'100%'" @chart-click="chartClick"></MyChart>
  </div>
</template>
<script>
import MyChart from '@/components/MyChart.vue'
export default {
  components: {
    MyChart
  },
  data () {
    return {}
  },
  computed: {
    op () {
      return {
        // ◆内置弹窗
        tooltip: {
          show: true
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: [
          {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            axisTick: {
              alignWithLabel: true
            }
          }
        ],
        yAxis: [
          {
            type: 'value'
          }
        ],
        series: [
          {
            type: 'bar',
            barWidth: '60%',
            data: [10, 52, 200, 334, 390, 330, 220],
            itemStyle: {
              color: '#5470c6'
            }
          }
        ]
      }
    }
  },
  methods: {
    chartClick () {}
  }
}
</script>
<style lang="less" scoped>
.echartBar {
  width: 400px;
  height: 300px;
}
</style>

2.弹窗效果展示

image.png

将鼠标悬浮在 Fri 柱条上,就出现原始弹窗内容啦。但是业务需求往往不一般,那我们如何修改弹窗的样式或内容呢?放心吧,ECharts 早就帮我们想好啦!

二、formatter 回调函数拼接弹窗

tooltip 里面还有一个 formatter 属性,支持字符串模板和回调函数两种形式,字符串模板这里就不研究了,我更热衷于使用回调函数,且功能更强大。

1.代码落地

<template>
  <div class="echartBar">
    <MyChart :op="op" :height="'100%'" @chart-click="chartClick"></MyChart>
  </div>
</template>
<script>
import MyChart from '@/components/MyChart.vue'
export default {
  components: {
    MyChart
  },
  data () {
    return {}
  },
  computed: {
    op () {
      return {
        // ◆内置弹窗
        // 1.拼接简易字符串
        // 如果没有配置 trigger 和 trigger ,则 params 是对象
        // tooltip: {
        //   formatter: (params) => {
        //     const tooltip = `${params.name}:${params.value}`
        //     return tooltip
        //   }
        // },
        // 2.拼接 HTML 模板
        // 如果配置 trigger 和 trigger ,则 params 是数组
        tooltip: {
          trigger: 'axis', // 触发类型
          axisPointer: { // 坐标指示器配置项
            type: 'shadow'
          },
          backgroundColor: 'transparent', // 去掉原始弹窗的背景色
          borderColor: 'transparent', // 去掉原始弹窗的边框
          formatter: (params) => {
            const tooltip =
              `<div class="tooltip">
                <div class="header">
                  <div class="riskColor ${this.getColor(params[0].value)}"></div>
                  <div class="riskRank">${this.getRiskRank(params[0].value)}风险</div>
                </div>
                <div class="body">${params[0].name}${params[0].value}</div>
              </div>`
            return tooltip
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: [
          {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            axisTick: {
              alignWithLabel: true
            }
          }
        ],
        yAxis: [
          {
            type: 'value'
          }
        ],
        series: [
          {
            name: 'Direct',
            type: 'bar',
            barWidth: '60%',
            data: [10, 52, 200, 334, 390, 330, 220],
            itemStyle: {
              color: '#5470c6'
            }
          }
        ]
      }
    }
  },
  methods: {
    chartClick () {},
    getColor (value) {
      let color = ''
      if (value > 300) {
        color = 'bgGreen'
      } else if (value > 200) {
        color = 'bgYellow'
      } else {
        color = 'bgRed'
      }
      return color
    },
    getRiskRank (value) {
      let risk = '无'
      if (value > 300) {
        risk = '低'
      } else if (value > 200) {
        risk = '中'
      } else {
        risk = '高'
      }
      return risk
    }
  }
}
</script>
<style lang="less" scoped>
.echartBar {
  width: 400px;
  height: 300px;
  /* 书写弹窗样式 */
  /deep/.tooltip {
    width: 100px;
    height: 50px;
    background-color: rgba(90,201,222,0.3);
    border: 1px solid #ccc;
    color: black;
    padding: 10px;
    border-radius: 10px;
    .header {
      display: flex;
      align-items: center;
      .riskColor {
        width: 20px;
        height: 20px;
        border-radius: 50%;
        margin-right: 5px;
        &.bgGreen {
          background-color: green;
        }
        &.bgYellow {
          background-color: yellow;
        }
        &.bgRed {
          background-color: red;
        }
      }
    }
    .body {
      margin-top: 10px;
    }
  }
}
</style>

2.弹窗效果展示

image.png

还是很不错的吧!一下子高级感就出来了。但是这样拼接一个弹窗出来,真的忒麻烦了。有没有更好的方法呢?比如,可以写一个弹窗组件,把它引进来?有,但是得升级 ECharts5 版本才行。

三、以组件的形式 new 一个弹窗

注意:第三种方法,对 ECharts 的版本要求比较高,我用的是 "echarts": "^5.0.0"

1.代码落地

@/components/MyTooltip.vue

<template>
  <div class="my-tooltip">
    <div class="tip-header">
      <div :class="['riskColor', getRiskColor(tipData.value)]"></div>
      <div class="riskRank">{{ getRiskRank(tipData.value) }}风险</div>
    </div>
    <div class="tip-body">{{ tipData.name }}:{{ tipData.value }}</div>
  </div>
</template>
<script>
export default {
  name: 'Tooltip',
  data () {
    return {
      tipData: {
        name: '',
        value: ''
      }
    }
  },
  methods: {
    // 获取风险等级颜色
    getRiskColor (value) {
      let color = 'bgGreen'
      if (value > 300) {
        color = 'bgGreen'
      } else if (value > 200) {
        color = 'bgYellow'
      } else {
        color = 'bgRed'
      }
      return color
    },
    // 获取风险等级
    getRiskRank (value) {
      let risk = '无'
      if (value > 300) {
        risk = '低'
      } else if (value > 200) {
        risk = '中'
      } else {
        risk = '高'
      }
      return risk
    },
    getData (params) {
      this.tipData = params
    }
  }
}
</script>
<style lang="less" scoped>
.my-tooltip {
  width: 100px;
  height: 50px;
  background-color: rgba(115,143,213,0.3);
  color: black;
  padding: 10px;
  border-radius: 10px;
  .tip-header {
    display: flex;
    align-items: center;
    .riskColor {
      width: 20px;
      height: 20px;
      border-radius: 50%;
      margin-right: 5px;
      background-color: green;
      color: blue;
      &.bgGreen {
        background-color: green;
      }
      &.bgYellow {
        background-color: yellow;
      }
      &.bgRed {
        background-color: red;
      }
    }
  }
  .tip-body {
    margin-top: 10px;
  }
}
</style>

App.vue

<template>
  <div class="echartBar">
    <MyChart :op="op" :height="'100%'" @chart-click="chartClick"></MyChart>
  </div>
</template>
<script>
import MyChart from '@/components/MyChart.vue'
import MyTooltip from '@/components/MyTooltip.vue'
import Vue from 'vue'
const MyTooltips = Vue.extend(MyTooltip)
export default {
  components: {
    MyChart
  },
  computed: {
    op () {
      return {
        // 内置弹窗
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          },
          backgroundColor: 'transparent',
          borderColor: 'transparent', // 设置颜色无效
          formatter: (params) => {
            const com = new MyTooltips()
            // com.getData({ name: params.name, value: params.value })
            com.getData({ name: params[0].name, value: params[0].value })
            com.$mount()
            // return com.$el.innerHTML // 用 innerHtml 会有问题,打印下就知道了
            return com.$el
          }
        },
        grid: {
          left: '3%',
          right: '4%',
          bottom: '3%',
          containLabel: true
        },
        xAxis: [
          {
            type: 'category',
            data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
            axisTick: {
              alignWithLabel: true
            }
          }
        ],
        yAxis: [
          {
            type: 'value'
          }
        ],
        series: [
          {
            name: 'Direct',
            type: 'bar',
            barWidth: '60%',
            data: [10, 52, 200, 334, 390, 330, 220],
            itemStyle: {
              color: '#5470c6'
            }
          }
        ]
      }
    }
  },
  methods: {
    chartClick () {}
  }
}
</script>
<style lang="less" scoped>
.echartBar {
  width: 400px;
  height: 300px;
}
</style>

2.弹窗效果展示

也可以实现吧!但是这个边框的颜色咋没生效嘞......这可能是一个 bug 吧,不知道哪位大佬能解决。

image.png

后语

下面讲讲我在第三种方法踩的坑:

1、不知道为啥, ECharts5 版本弹窗的原始边框颜色改不掉,所以就算你在 tooltip 里面设置 backgroundColor: 'transparent',最外层也始终有一层背景,所以我的办法是直接在 tooltip 里面设置弹窗的背景颜色,边框的颜色就不管了。

2、当我设置 triggeraxisPointer 的时候, formatter 回调函数里面的参数 params 竟然由对象变成了数组,这就导致我无法获取 params 里面的 namevalue。我折腾了很久才发现问题所在。

  // com.getData({ name: params.name, value: params.value })
  com.getData({ name: params[0].name, value: params[0].value })

3、最开始我用的是 reurn com.$el.innerHTML,样式无法生效,各种升级版本都没有用,打印才知道,弹窗最外层的盒子没有了,也就 my-tooltip 类名根本不在里面,会生效才怪!所以,return com.el 就好了。

// return com.$el.innerHTML 
return com.$el

最后贴上我的 package.json 吧,因为碰到很多问题都是版本的原因。除了第三种方法我用的 echarts@5.0.0,其他都是 echarts@4.9.0

{
  "name": "my-echarts",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "echarts": "^4.9.0",
    "vue": "^2.6.14",
    "vue-router": "^3.2.0",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.15",
    "@vue/cli-plugin-eslint": "~4.5.15",
    "@vue/cli-plugin-router": "~4.5.15",
    "@vue/cli-plugin-vuex": "~4.5.15",
    "@vue/cli-service": "~4.5.15",
    "@vue/eslint-config-standard": "^5.1.2",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "less": "^3.0.4",
    "less-loader": "^5.0.0",
    "vue-template-compiler": "^2.6.14"
  }
}