跳转到内容

柱状图每个栏目上面显示数据

@ep_src_echarts_example_1_Example(./Example.vue)
vue
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import VChart from 'vue-echarts'
import { use } from 'echarts'
import { CustomChart } from 'echarts/charts'

// @ts-ignore 类型报错,懒得解决
use([CustomChart])

// 辅助函数:添加千分位分隔符
const addThousandsSeparator = (num: number | string, decimal = 0) => {
  const [int, dec = ''] = String(num).split('.')
  const formatted = int.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
  if (decimal > 0) {
    return `${formatted}.${dec.slice(0, decimal).padEnd(decimal, '0')}`
  } else {
    return formatted
  }
}

const data = [
  { name: '产品A', revenue: 10000, cost: 6000 },
  { name: '服务B', revenue: 8500, cost: 5100 },
  { name: '商品C', revenue: 15000, cost: 12000 },
  { name: '订阅D', revenue: 5000, cost: 1000 },
  { name: '定制E', revenue: 12000, cost: 9000 },
]

const option = {
  tooltip: {
    trigger: 'axis',
  },
  grid: {
    top: 90,
    bottom: 20,
    right: '5%',
  },
  xAxis: {
    type: 'category',
    data: data.map((v) => v.name),
  },
  yAxis: {
    type: 'value',
  },
  series: [
    {
      type: 'bar',
      name: '实收',
      data: data.map((v) => v.revenue),
    },
    {
      type: 'bar',
      name: '成本',
      data: data.map((v) => v.cost),
    },
    {
      type: 'custom',
      data: Array(data.length).fill(0),
      // 不要在 tooltip 中显示这个数据
      tooltip: { show: false },
      z: 3,
      renderItem(params: any, api: any) {
        const item = data[params.dataIndex]
        // 毛利率
        const rate = ((item.revenue - item.cost) / item.revenue) * 100

        const [x, y] = api.coord([
          api.value(0),
          Math.max(item.revenue, item.cost),
        ])

        //                                   20 等于 grid.bottom
        const enterY = api.getHeight() - y - 20

        const children: any = [
          {
            type: 'text',
            style: {
              text: addThousandsSeparator(item.cost),
              x: x,
              y: y - 22,
              fill: '#91cc75',
              fontSize: 16,
              fontWeight: 700,
              textAlign: 'center',
            },
            enterFrom: { y: enterY },
            during: (duringAPI: any) => {
              const liveY = duringAPI.getTransform('y')
              const progress = 1 - liveY / enterY
              duringAPI.setStyle(
                'text',
                addThousandsSeparator(item.cost * progress),
              )
            },
          },
          {
            type: 'text',
            style: {
              text: addThousandsSeparator(item.revenue),
              x: x,
              y: y - 40,
              fill: '#5470c6',
              fontSize: 16,
              fontWeight: 700,
              textAlign: 'center',
            },
            enterFrom: { y: enterY },
            during: (duringAPI: any) => {
              const liveY = duringAPI.getTransform('y')
              const progress = 1 - liveY / enterY
              duringAPI.setStyle(
                'text',
                addThousandsSeparator(item.revenue * progress),
              )
            },
          },
          {
            type: 'rect',
            shape: {
              x: x - 40,
              y: y - 86,
              width: 80,
              height: 40,
            },
            style: {
              fill: '#EAF7F9',
              stroke: '#0CA7B8',
            },
            enterFrom: { y: enterY },
          },
          {
            type: 'text',
            style: {
              text: rate.toFixed(2) + '%',
              x: x,
              y: y - 83,
              fill: '#0CA7B8',
              fontSize: 16,
              fontWeight: 700,
              textAlign: 'center',
            },
            enterFrom: { y: enterY },
            during: (duringAPI: any) => {
              const liveY = duringAPI.getTransform('y')
              const progress = 1 - liveY / enterY
              duringAPI.setStyle(
                'text',
                (rate * progress).toFixed(2) + '%',
              )
            },
          },
          {
            type: 'text',
            style: {
              text: '毛利率',
              x: x,
              y: y - 63,
              fill: '#222222',
              fontSize: 12,
              textAlign: 'center',
            },
            enterFrom: { y: enterY },
          },
        ]

        return {
          type: 'group',
          children,
        }
      },
    },
  ],
}

const show = ref(true)

const handleRerender = async () => {
  show.value = false
  await nextTick()
  show.value = true
}
</script>

<template>
  <button @click="handleRerender">重新渲染</button>
  <v-chart
    v-if="show"
    :option="option"
    autoresize
    style="width: 100%; height: 400px"
  />
</template>