概要
需求需要实现图例移入显示描述说明
故实现自定义图例
技术细节
<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>
效果