echarts 将图例(legend)拆分成左右两栏

在多数情况下,我们的图形报表中要么不显示图例组件,要么只是将图例组件固定在上/下/左/右的某一个方向上,但是现在有个需求,是需要将图例组件进行拆分成两栏,分别在左右两侧进行展示。

测试环境

  • Vue 3.x
  • echarts 5.x
  • TypeScript

准备测试数据

const data: Array<{ name: string; value: number }> = new Array(7)
    .fill(0)
    .map((_, i) => {
        return { name: `随机数${i + 1}`, value: Math.ceil(Math.random() * 1000) + 100 }
    })
    .sort((a, b) => b.value - a.value)
const total = data.map((element) => element.value).reduce((a, b) => a + b, 0)

data 就是我们的图表数据,是一个一维对象数组,每个对象包含 namevalue 两个属性,value 是一个随机产生的数值,total 是所有 value 的总和,用于后续计算百分比。

我们需要展示的是一个南丁格尔玫瑰图,递减或递增的数据展示出来的图表才好看,所以对 data 进行了排序处理。

图例数据

echarts 的 legend 配置项可以是单个对象,也可以是一个对象数组,既然我们想要将图例拆分成左右两栏,那就必须是一个数组了,数组的第一个元素是左侧的图例,第二个元素是右侧图例。

[0, 1].map((i) => {
    const mid = Math.ceil(data.length / 2)
    const legendItem: echarts.LegendComponentOption = {
        orient: 'vertical',
        data: (i === 0 ? data.slice(0, mid) : data.slice(mid)).map((element) => element.name),
        icon: 'rect',
        itemWidth: 10,
        itemHeight: 10,
        itemGap: 24,
        top: 'center',
        formatter: (name) => {
            const item = data.find((element) => element.name === name)
            if (item) {
                const percentage = ((item.value / total) * 100).toFixed(2).replace(/\.0+$/, '')
                return `{name|${item.name}}{value|${percentage}%}`
            }
            return name
        },
        textStyle: {
            color: 'inherit',
            rich: {
                name: {
                    align: 'left',
                    width: 70,
                    fontSize: 14
                },
                value: {
                    align: 'right',
                    width: 50,
                    fontSize: 14
                }
            }
        }
    }
    if (i === 0) {
        legendItem.left = 'left' // 居左显示
    } else {
        legendItem.right = 'right' // 居右显示
    }
    return legendItem
})

通过 mid 将数据拆分成两份,然后再分别设置 leftright 属性,使其居左/居右显示。最后通过 formatter 格式器和 textStyle 的富文本样式来自定义图例上的文本内容,通过 formatter 将百分比显示出来,然后通过 textStyle.rich 来设置文本的对齐方式。

series

最后通过 series 属性来设置我们的图表样式。

[
    {
        type: 'pie',
        radius: ['30%', '50%'],
        center: ['50%', '50%'],
        roseType: 'area',
        label: {
            show: true,
            color: 'inherit',
            fontSize: 16,
            fontWeight: 'bold',
            formatter: '{c}'
        },
        data: data
    }
]

完整示例

<script setup lang="ts">
import { onMounted } from 'vue'
import * as echarts from 'echarts'

onMounted(() => {
    const data: Array<{ name: string; value: number }> = new Array(7)
        .fill(0)
        .map((_, i) => {
            return { name: `随机数${i + 1}`, value: Math.ceil(Math.random() * 1000) + 100 }
        })
        .sort((a, b) => b.value - a.value)
    const total = data.map((element) => element.value).reduce((a, b) => a + b, 0)

    const options: echarts.EChartsOption = {
        legend: [0, 1].map((i) => {
            const mid = Math.ceil(data.length / 2)
            const legendItem: echarts.LegendComponentOption = {
                orient: 'vertical',
                data: (i === 0 ? data.slice(0, mid) : data.slice(mid)).map((element) => element.name),
                icon: 'rect',
                itemWidth: 10,
                itemHeight: 10,
                itemGap: 24,
                top: 'center',
                formatter: (name) => {
                    const item = data.find((element) => element.name === name)
                    if (item) {
                        const percentage = ((item.value / total) * 100).toFixed(2).replace(/\.0+$/, '')
                        return `{name|${item.name}}{value|${percentage}%}`
                    }
                    return name
                },
                textStyle: {
                    color: 'inherit',
                    rich: {
                        name: {
                            align: 'left',
                            width: 70,
                            fontSize: 14
                        },
                        value: {
                            align: 'right',
                            width: 50,
                            fontSize: 14
                        }
                    }
                }
            }
            if (i === 0) {
                legendItem.left = 'left'
            } else {
                legendItem.right = 'right'
            }
            // legendItem.left = i === 0 ? 'left' : 'right'
            return legendItem
        }),
        tooltip: {
            trigger: 'item',
            formatter: '{b}: {c}({d}%)'
        },
        series: [
            {
                type: 'pie',
                radius: ['30%', '50%'],
                center: ['50%', '50%'],
                roseType: 'area',
                label: {
                    show: true,
                    // 5.x以上版本需要显式设置为 inherit,否则标签颜色将显示为黑色
                    color: 'inherit',
                    fontSize: 16,
                    fontWeight: 'bold',
                    formatter: '{c}'
                },
                data: data
            }
        ]
    }
    const chartInstance = echarts.init(document.getElementById('chart')!)
    chartInstance.setOption(options)
})
</script>

<template>
    <div id="chart" style="width: 650px; height: 300px"></div>
</template>

<style lang="scss"></style>

left/right属性的差异

在本例中,图例的居左/居右显示是通过设置 left: 'left'/right: 'right' 来实现的,其实仅靠设置 left 属性也是可以实现的,但是如果按照 left: 'right' 这样将图例进行居右显示,不仅是图例整体居右,还会影响到图例的图标位置,但是通过分别设置 leftright 属性的话,则图例的图标一直都是显示在左侧。