vue3 echarts ts 环形图中间文字 样式设置
vue3 echarts ts 环形图中间文字 样式设置
一、背景与问题
在数据可视化场景中,环形图常用于展示占比关系。ECharts 提供了完整的环形图支持,但其默认的中间文字样式(如百分比、标题等)往往无法满足复杂业务需求。例如:
- 业务场景需要多行文本
- 需要动态切换文本内容
- 需要特殊字体/渐变/阴影效果
- 需要响应式调整文本位置
传统做法需要通过 label 配置项控制,但容易出现文本重叠、样式丢失等问题。本文将深入解析 ECharts 环形图中间文字样式设置的底层原理,并提供可复用的解决方案。
二、基本原理
ECharts 环形图的中间文字主要通过 series.label 配置项控制。其工作原理如下:
- 渲染机制:当
radius设置为['20%', '40%']时,图表会在环形区域中心生成一个文本元素 - 文本定位:通过
label的formatter控制内容,position控制位置(默认为 center) - 样式控制:通过
rich配置实现复杂样式,textStyle控制基本样式 - 特殊需求:需要通过
labelLine控制连接线,emphasis控制高亮状态
关键配置项结构:
series: {
type: 'pie',
radius: ['20%', '40%'],
label: {
formatter: '{b}: {d}%',
position: 'center',
textStyle: {
fontSize: 20,
color: '#fff'
}
},
labelLine: {
show: false
}
}三、环境准备
确保项目已安装必要依赖:
npm install echarts @types/echarts --save创建 Vue3 + TS 组件结构:
// src/components/RingChart.vue
<template>
<div ref="chartRef" class="chart-container"></div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onBeforeUnmount, ref } from 'vue'
import * as echarts from 'echarts'
export default defineComponent({
name: 'RingChart',
setup() {
const chartRef = ref<HTMLElement | null>(null)
const chartInstance = ref<echarts.ECharts | null>(null)
const initChart = () => {
if (!chartRef.value) return
chartInstance.value = echarts.init(chartRef.value)
// 初始化配置
}
onMounted(() => {
initChart()
})
onBeforeUnmount(() => {
chartInstance.value?.dispose()
})
return { chartRef }
}
})
</script>
<style scoped>
.chart-container {
width: 100%;
height: 400px;
}
</style>四、核心实现
1. 基础样式设置
const option = {
series: [{
type: 'pie',
radius: ['20%', '40%'],
data: [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
],
label: {
formatter: '{b}: {d}%',
position: 'center',
textStyle: {
fontSize: 24,
color: '#ffffff',
fontWeight: 'bold'
}
},
labelLine: {
show: false
}
}]
}关键代码解释:
formatter支持模板字符串,可自定义显示内容textStyle控制字体样式,支持所有 CSS 样式属性position: 'center'确保文本位于环形中心
2. 复杂样式设置
const option = {
series: [{
type: 'pie',
radius: ['20%', '40%'],
data: [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
],
label: {
formatter: '{b}: {d}%',
position: 'center',
rich: {
name: {
fontSize: 24,
color: '#FFD700'
},
value: {
fontSize: 16,
color: '#FFFFFF'
}
},
textStyle: {
color: 'transparent'
}
},
labelLine: {
show: false
}
}]
}关键代码解释:
rich配置允许定义多个样式块textStyle.color: 'transparent'使默认文字透明,只显示 rich 中定义的样式- 这种方式可实现多行文本样式差异化
3. 动态文本设置
const option = {
series: [{
type: 'pie',
radius: ['20%', '40%'],
data: [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
],
label: {
formatter: (params: any) => {
const total = params.seriesData.reduce((sum, d) => sum + d.value, 0)
return `Total: ${total} / ${params.seriesData.length} Items`
},
position: 'center',
textStyle: {
fontSize: 20,
color: '#00FF00'
}
},
labelLine: {
show: false
}
}]
}关键代码解释:
formatter可接受参数,支持动态计算- 通过
params可获取系列数据、当前数据点等信息 - 适用于需要动态计算总和、平均值等场景
五、完整案例
1. 响应式环形图组件
// src/components/ResponsiveRingChart.vue
<template>
<div ref="chartRef" class="chart-container"></div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onBeforeUnmount, ref, watch } from 'vue'
import * as echarts from 'echarts'
export default defineComponent({
name: 'ResponsiveRingChart',
props: {
data: {
type: Array as () => Array<{ value: number; name: string }>,
default: () => [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
]
},
radius: {
type: Array,
default: () => ['20%', '40%']
},
title: {
type: String,
default: '环形图'
}
},
setup(props) {
const chartRef = ref<HTMLElement | null>(null)
const chartInstance = ref<echarts.ECharts | null>(null)
const chartWidth = ref(0)
const chartHeight = ref(0)
const initChart = () => {
if (!chartRef.value) return
chartInstance.value = echarts.init(chartRef.value)
const option: echarts.EChartOption = {
title: {
text: props.title,
left: 'center',
textStyle: {
fontSize: 20,
color: '#333'
}
},
series: [{
type: 'pie',
radius: props.radius,
data: props.data,
label: {
formatter: (params: any) => {
const total = params.seriesData.reduce((sum, d) => sum + d.value, 0)
return `${params.name}: ${((params.value / total) * 100).toFixed(1)}%`
},
position: 'center',
rich: {
percent: {
fontSize: 24,
color: '#FFD700'
},
name: {
fontSize: 16,
color: '#FFFFFF'
}
},
textStyle: {
color: 'transparent'
}
},
labelLine: {
show: false
}
}]
}
chartInstance.value.setOption(option)
// 响应式调整
const resizeHandler = () => {
if (chartRef.value) {
const width = chartRef.value.clientWidth
const height = chartRef.value.clientHeight
chartInstance.value?.resize()
// 可选:动态调整文本大小
if (width < 300) {
option.series[0].label.textStyle.fontSize = 12
} else {
option.series[0].label.textStyle.fontSize = 20
}
chartInstance.value.setOption(option)
}
}
window.addEventListener('resize', resizeHandler)
watch(() => props.data, () => {
if (chartInstance.value) {
chartInstance.value.setOption({
series: [{
data: props.data
}]
})
}
})
}
onMounted(() => {
initChart()
})
onBeforeUnmount(() => {
chartInstance.value?.dispose()
window.removeEventListener('resize', resizeHandler)
})
return { chartRef }
}
})
</script>
<style scoped>
.chart-container {
width: 100%;
height: 400px;
background: #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
</style>关键实现说明:
- 响应式设计:通过
resize事件动态调整图表尺寸 - 动态样式:根据容器大小调整文本字号
- 数据绑定:通过
watch监听 props 变化 - 安全考虑:避免直接操作 DOM 元素
六、源码解析
以 formatter 函数为例,其内部调用链如下:
formatter被注册到series.label配置- 在
render阶段,ECharts 会调用formatter函数 函数接收
params参数,包含:params.name: 数据项名称params.value: 数据值params.percent: 占比params.seriesData: 当前系列的所有数据
- 返回的字符串被渲染为文本内容
// ECharts 源码片段(简化版)
function renderLabel(params: any, label: any) {
const text = label.formatter(params)
const textElement = createTextElement(text)
textElement.style = label.textStyle
textElement.rich = label.rich
// ...其他渲染逻辑
}七、进阶使用
1. 动态样式切换
const option = {
series: [{
type: 'pie',
radius: ['20%', '40%'],
data: [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
],
label: {
formatter: (params: any) => {
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches
const color = isDarkMode ? '#FFD700' : '#00FF00'
return `${params.name}: ${params.value} ${color}`
},
position: 'center',
textStyle: {
color: 'transparent'
}
}
}]
}2. 动画效果控制
const option = {
series: [{
type: 'pie',
radius: ['20%', '40%'],
data: [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
],
label: {
formatter: '{b}: {d}%',
position: 'center',
textStyle: {
fontSize: 20,
color: '#ffffff'
}
},
labelLine: {
show: true
},
animation: false // 关闭动画
}]
}八、性能与工程实践
1. 性能优化
- 避免频繁重绘:使用
setOption的notMerge参数控制是否合并配置 - 文本缓存:对于固定文本内容,可使用
textStyle配置而非rich - 动态样式:避免在
formatter中执行复杂计算
const option = {
series: [{
type: 'pie',
radius: ['20%', '40%'],
data: [
{ value: 335, name: 'A' },
{ value: 310, name: 'B' },
{ value: 270, name: 'C' },
{ value: 150, name: 'D' }
],
label: {
formatter: '{b}: {d}%',
position: 'center',
textStyle: {
fontSize: 20,
color: '#ffffff'
}
}
}]
}2. 安全风险
- XSS 防护:确保
formatter中的动态内容经过转义 - 数据验证:对传入的
data进行格式校验 - 权限控制:避免将敏感数据直接渲染为文本
3. 工程实践建议
使用 TypeScript 定义配置项类型:
type RingChartOption = { title: string series: Array<{ type: 'pie' radius: [string, string] data: Array<{ value: number; name: string }> label: { formatter: (params: any) => string position: string textStyle: { fontSize: number color: string } } }> }建立配置项工厂函数:
const createOption = (data: Array<{ value: number; name: string }>): echarts.EChartOption => { return { series: [{ type: 'pie', radius: ['20%', '40%'], data, label: { formatter: '{b}: {d}%', position: 'center', textStyle: { fontSize: 20, color: '#ffffff' } } }] } }
九、常见问题与踩坑
1. 文字重叠问题
问题表现:文本显示在环形图外或被遮挡
解决办法:
- 调整
radius值 - 使用
labelLine控制连接线 - 通过
padding调整容器边距
2. 样式未生效
常见原因:
- 未正确设置
textStyle或rich配置 - 使用了错误的配置项(如
label.textStyle而非label.textStyle) - 未正确引入 ECharts 库
3. 响应式失效
解决办法:
- 监听
resize事件 - 在
resize时重新设置option - 使用
echarts.init时传入容器尺寸
4. 动态内容渲染错误
错误示例:
formatter: (params: any) => {
return eval(`params.${someDynamicKey}`)
}改进方案:
formatter: (params: any) => {
const key = someDynamicKey
return params[key] || 'N/A'
}十、最佳实践
- 优先使用
rich配置:实现复杂样式时更灵活 - 避免频繁更新配置:使用
notMerge参数优化性能 - 使用 TypeScript 类型:确保配置项类型安全
- 考虑响应式设计:动态调整文本样式和位置
- 注意安全性:对动态内容进行转义处理
- 使用工厂函数:提高代码可维护性
- 合理使用动画:避免过度动画影响性能
十一、总结
通过深入分析 ECharts 环形图中间文字样式设置的原理,我们了解到其底层实现机制和配置项关系。本文提供了多个代码示例,涵盖基础样式、复杂样式、动态内容等场景,同时给出了完整的响应式组件实现。
在实际开发中,建议根据具体需求选择合适的实现方式:
- 简单样式:直接使用
textStyle配置 - 复杂样式:使用
rich实现多样式块 - 动态内容:通过
formatter实现动态计算 - 响应式需求:结合
resize事件和容器尺寸调整
需要注意的是,这种方案适合需要突出显示中间文字的场景(如仪表盘、统计面板),但不适合需要频繁交互或复杂动画的场景。在实现过程中应特别注意性能优化和安全性问题,确保最终效果符合业务需求。
评论已关闭