vue3.2 + echarts5.6 + ant-design-vue 3.x 实现自定义 echarts 图例

发布于:2025-07-28 ⋅ 阅读:(17) ⋅ 点赞:(0)

文章目录

概要

需求需要实现图例移入显示描述说明
故实现自定义图例

技术细节

<template>
  <div class="custom-legend">
    <div
      v-for="item in legends"
      :key="item.name"
      class="legend-item"
      :class="{ 'is-disabled': !isItemActive(item.name) }"
      @click="toggleLegend(item)"
    >
      <span
        class="legend-marker"
        :style="{ backgroundColor: item.color }"
      ></span>
      <a-tooltip>
        <template #title>
          <span>{{ item.tooltip || item.name }}</span>
        </template>
        <span>{{ item.name }}</span>
      </a-tooltip>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue'

const props = defineProps({
  chartInstance: {
    type: Object,
    default: () => null
  },
  customTooltips: {
    type: Object,
    default: () => ({})
  }
})

const legends = ref([])
const activeMap = ref({})

const isItemActive = computed(() => (name) => {
  return activeMap.value[name]
})

const toggleLegend = (item) => {
  if (!props.chartInstance) return
  const name = item.name
  const newState = !activeMap.value[name]
  activeMap.value = { ...activeMap.value, [name]: newState }
  props.chartInstance.setOption({
    legend: {
      selected: activeMap.value
    }
  })
}

const renderLegends = () => {
  if (!props.chartInstance) {
    legends.value = []
    return
  }
  const chart = props.chartInstance
  const options = chart.getOption()
  const { series, color: globalColors, legend } = options
  const chartSelectedMap = legend?.[0]?.selected || {}
  activeMap.value = { ...chartSelectedMap }
  const legendItems = []
  series.forEach((seriesItem, seriesIndex) => {
    if (seriesItem.type === 'pie') {
      seriesItem.data.forEach((dataItem, dataIndex) => {
        const name = dataItem.name
        const color =
          dataItem.itemStyle?.color ||
          globalColors[dataIndex % globalColors.length] ||
          '#000'

        if (activeMap.value[name] === undefined) {
          activeMap.value[name] = true
        }

        legendItems.push({
          name,
          color,
          tooltip: props.customTooltips[name] || name
        })
      })
    } else {
      const name = seriesItem.name
      const color =
        seriesItem.itemStyle?.color ||
        globalColors[seriesIndex % globalColors.length] ||
        '#000'

      if (activeMap.value[name] === undefined) {
        activeMap.value[name] = true
      }

      legendItems.push({
        name,
        color,
        tooltip: props.customTooltips[name] || name
      })
    }
  })

  legends.value = legendItems
}

const handleChartLegendChange = (params) => {
  activeMap.value = { ...params.selected }
}

watch(
  () => props.chartInstance,
  (newVal) => {
    if (newVal) {
      renderLegends()
    } else {
      legends.value = []
    }
  },
  { immediate: true }
)
</script>

<style scoped lang="scss">
.custom-legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  padding: 8px;

  .legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    transition: all 0.2s ease;

    .legend-marker {
      width: 14px;
      height: 14px;
      border-radius: 50%;
      transition: all 0.2s ease;
    }

    &.is-disabled {
      opacity: 0.6;
      .legend-marker {
        filter: grayscale(100%);
        transform: scale(0.9);
      }
    }

    &:hover:not(.is-disabled) {
      transform: translateY(-1px);
      opacity: 0.9;
    }
  }
}
</style>

<template>
  <div class="container">
    <div class="chart" ref="lineChart"></div>
    <div class="chart" ref="pieChart"></div>
    <div class="legend1">
      <Legend :chartInstance="chartInstance1" />
    </div>
    <div class="legend2">
      <Legend :chartInstance="chartInstance2" />
    </div>
  </div>
</template>
<script setup>
import * as echarts from 'echarts'
import Legend from './components/Legend.vue'

const lineChart = ref(null)
const pieChart = ref(null)

const chartInstance1 = shallowRef(null)
const chartInstance2 = shallowRef(null)

const _data = {
  line: {
    title: '折线图示例',
    categories: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
    legend: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
    series: [
      {
        name: '邮件营销',
        type: 'line',
        data: [120, 132, 101, 134, 90, 230, 210]
      },
      {
        name: '联盟广告',
        type: 'line',
        data: [220, 182, 191, 234, 290, 330, 310]
      },
      {
        name: '视频广告',
        type: 'line',
        data: [150, 232, 201, 154, 190, 330, 410]
      },
      {
        name: '直接访问',
        type: 'line',
        data: [320, 332, 301, 334, 390, 330, 320]
      },
      {
        name: '搜索引擎',
        type: 'line',
        data: [820, 932, 901, 934, 1290, 1330, 1320]
      }
    ]
  },

  pie: {
    title: '饼图示例',
    legend: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎'],
    series: [
      { value: 335, type: 'pie', name: '直接访问' },
      { value: 310, type: 'pie', name: '邮件营销' },
      { value: 234, type: 'pie', name: '联盟广告' },
      { value: 135, type: 'pie', name: '视频广告' },
      { value: 1548, type: 'pie', name: '搜索引擎' }
    ]
  }
}

const onItemClick = (params) => {
  chartInstance1.value.dispatchAction({
    type: 'legendToggleSelect',
    name: params.name
  })
}

const initChart = () => {
  const lineOption = {
    title: {
      text: _data.line.title
    },
    tooltip: {
      trigger: 'axis'
    },
    legend: {
      show: false
    },
    grid: {
      left: '3%',
      right: '4%',
      bottom: '3%',
      containLabel: true
    },
    xAxis: {
      type: 'category',
      boundaryGap: false,
      data: _data.line.categories
    },
    yAxis: {
      type: 'value'
    },
    series: _data.line.series
  }

  const pieOption = {
    title: {
      text: _data.pie.title
    },
    tooltip: {
      trigger: 'item',
      formatter: '{a} <br/>{b} : {c} ({d}%)'
    },
    legend: {
      show: false
    },
    series: [
      {
        name: '访问来源',
        type: 'pie',
        radius: '55%',
        center: ['50%', '60%'],
        data: _data.pie.series,
        itemStyle: {
          emphasis: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: 'rgba(0, 0, 0, 0.5)'
          }
        }
      }
    ]
  }

  const lineChartDom = lineChart.value
  const pieChartDom = pieChart.value

  const lineChartInstance = echarts.init(lineChartDom)
  const pieChartInstance = echarts.init(pieChartDom)

  lineChartInstance.setOption(lineOption)
  pieChartInstance.setOption(pieOption)

  chartInstance1.value = lineChartInstance
  chartInstance2.value = pieChartInstance
}

onMounted(() => {
  initChart()
})
</script>

<style lang="scss" scoped>
.container {
  display: flex;
  width: 1200px;
  justify-content: space-between;
  margin-top: 20px;
  position: relative;
  .chart {
    width: 49%;
    height: 400px;
  }
  .legend1 {
    position: absolute;
    bottom: -50px;
    left: 50px;
  }
  .legend2 {
    position: absolute;
    bottom: -50px;
    right: 50px;
  }
}
</style>

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到