封装一个饼图组件 发表于 2024-07-13 最近因为工作需要,封装了一个饼图组件。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705<template> <!-- 左侧图标区 --> <div class="container"> <div ref="pieChart" id="charts"></div> <div v-if="chartOptions.isCustomLegend" class="custom-legend-container" :style="chartOptions.customLegendBox" > <div v-for="(legend, index) in chartOptions.chartData" :key="legend.name" @click="toggleSeries(index)" class="custom-legend" :class="`custom-legend-${index + 1}`" > <div class="custom-legend-info"> <div style="width: 20px; height: 10px" :style="{ backgroundColor: chartOptions.colors ? chartOptions.colors[index] : colors[index], }" ></div> <div class="name">{{ legend.name }}</div> </div> <div class="custom-legend-value">{{ legend.value }}</div> </div> <div v-if="chartOptions.customLegendTitle.text" class="custom-legend-title" :style="chartOptions.customLegendTitle" > {{ chartOptions.customLegendTitle.text }} </div> </div> <div v-if="chartOptions.isCustomLegend" class="custom-subtitle" :style="chartOptions.customSubTitle" > {{ subTitle }} </div> </div></template><script> import * as echarts from 'echarts' import { deepMerge } from '@/utils/index' export default { name: 'CgPieChart', data() { return { roseTypeTitle: '', roseTypeColor: '#fff', // 南丁格尔图文字和延伸线的颜色 defaultOptions: {}, chartInstance: null, colors: ['#9D2932', '#789262', '#494166', '#D9B612', '#177CB0'], title: '总数(件)', padAngle: 3, // 扇区间距 在chartOptions.isSpace为true的情况下起作用 } }, computed: { subTitle() { const dataNum = this.chartOptions.chartData.reduce((pre, cur) => { return pre + cur.value }, 0) return dataNum }, selected() { const res = {} this.chartOptions.chartData.forEach((item, index) => { res[item.name] = true }) return res }, }, props: { chartOptions: { type: Object, default: () => ({}), }, }, methods: { // 点击显示/隐藏legend toggleSeries(index) { const legendName = this.chartOptions.chartData[index].name this.selected[legendName] = !this.selected[legendName] // 切换选中状态 this.updateChart() }, handleResize() { if (this.chartInstance) { this.chartInstance.resize() } }, // 初始化echarts initChart() { const chartContainer = this.$refs.pieChart console.log('chartContainer', chartContainer) this.chartInstance = echarts.init(chartContainer) this.updateChart() }, // 生成居中显示图例的配置项 generateAlignOptions() { // 数据总数 const dataNum = this.chartOptions.chartData.reduce((pre, cur) => { return pre + cur.value }, 0) // 颜色 const colors = this.chartOptions.colors || this.colors // 如果配置了显示总数,那么就在标题后面加上数字总和 const showNumTitle = `${ this.chartOptions.title ? this.chartOptions.title : this.title }:${dataNum}` // 默认标题 const defaultTitle = this.chartOptions.title ? this.chartOptions.title : this.title const options = { title: { textStyle: { color: '#fff', fontSize: 14, }, itemGap: 5, // right: "15%", left: '55%', // top: "29%", top: '18%', text: this.chartOptions.showNumTitle ? showNumTitle : defaultTitle, }, tooltip: { show: true, trigger: 'item', backgroundColor: '#000342', textStyle: { color: '#fff', }, }, legend: { type: 'scroll', right: '18%', top: '39%', //居中显示 bottom: 20, orient: 'vertical', //垂直 itemGap: 10, //图例间距 itemHeight: 10, //图例高度 itemWidth: 30, //图例宽度 data: this.chartOptions.chartData.map((item, i) => ({ name: item.name, textStyle: { color: colors[i], }, })), formatter: (name) => { const dataItem = this.chartOptions.chartData.find( (item) => item.name === name ) if (dataItem) { // 使用获取到的颜色设置文本标记b的颜色 return [`{name|${name}}{value|${dataItem.value}}`] } else { return name // 如果没有找到匹配的数据项,则返回原始名称 } }, textStyle: { color: '#fft', rich: { name: { // legend左边的文字 fontSize: 12, color: '#fft', align: 'left', width: 50, padding: [0, 10, 0, 10], // 1.左边的文字添加右边距10(可自己调整) }, value: { // legend右边的值 fontSize: 12, backgroundColor: 'transparent', // 2.右边的值添加背景色 align: 'right', // 3.右对齐 padding: [0, 0, 0, 0], // 4.设置右边距为-100(-70/-80..可自己调整) }, }, }, }, series: [ { type: 'pie', radius: ['40%', '60%'], center: ['30%', '45%'], padAngle: this.chartOptions.isSpace ? this.chartOptions.padAngle || this.padAngle : 0, itemStyle: { borderColor: '#fff', borderWidth: 0, color: (params) => { const index = params.dataIndex return colors[index] }, }, label: { show: false, position: 'center', }, labelLine: { show: false, }, data: this.chartOptions.chartData.map((item, index) => { item.label = { color: colors[index], } return item }), }, ], } return options }, // 生成自定义显示图例的配置项 generateCustomOptions() { // 数据总数 const dataNum = this.chartOptions.chartData.reduce((pre, cur) => { return pre + cur.value }, 0) // 颜色 const colors = this.chartOptions.colors || this.colors // 如果配置了显示总数,那么就在标题后面加上数字总和 const showNumTitle = `${ this.chartOptions.title ? this.chartOptions.title : this.title }:${dataNum}` // 自定义图例位置下显示的title const options = { title: { text: '总数', top: 0, left: 10, textStyle: { color: '#f2f2f2', fontSize: 20, width: '100%', }, }, tooltip: { show: true, trigger: 'item', backgroundColor: '#000342', textStyle: { color: '#fff', }, }, legend: [], series: [ { type: 'pie', radius: ['40%', '60%'], center: ['30%', '45%'], padAngle: this.chartOptions.isSpace ? this.chartOptions.padAngle || this.padAngle : 0, label: { show: false, position: 'center', }, labelLine: { show: false, }, data: this.chartOptions.chartData .map((item, index) => { item.itemStyle = { color: colors[index], } return item }) .filter((item) => this.selected[item.name]), }, ], } return options }, // 生成南丁格尔图的series generateRoseOptions() { const options = { title: { text: this.chartOptions.roseTypeTitle || this.roseTypeTitle, left: '20%', top: 10, textStyle: { color: this.chartOptions.roseTypeColor || this.roseTypeColor, fontSize: 16, }, }, tooltip: { show: true, trigger: 'item', backgroundColor: '#000342', textStyle: { color: '#fff', }, }, series: [ { type: 'pie', radius: '45%', center: ['50%', '30%'], roseType: 'area', itemStyle: { borderRadius: 0, borderWidth: 5, }, data: this.chartOptions.chartData, label: { normal: { // formatter: "{font|{b}}\n{font|警情:{c}}\n{font|转化率:{percent}}", formatter: (params) => { return `{font|${params.name}}\n{font|警情:${params.value}}\n{font|转化率:${params.data.percent}}` }, rich: { font: { fontSize: 14, padding: [5, 0], color: this.chartOptions.roseTypeColor || this.roseTypeColor, }, }, }, }, labelLine: { lineStyle: { color: this.chartOptions.roseTypeColor || this.roseTypeColor, }, }, }, { type: 'pie', hoverAnimation: false, tooltip: { show: false, }, z: -1, radius: ['55%'], center: ['50%', '30%'], label: { show: false, }, data: [ { value: 100, itemStyle: { color: '#092852', }, }, ], }, { type: 'pie', hoverAnimation: false, tooltip: { show: false, }, z: 2, radius: ['10%'], center: ['50%', '30%'], label: { show: false, }, data: [ { value: 100, itemStyle: { color: '#000000', }, }, ], }, { type: 'pie', hoverAnimation: false, tooltip: { show: false, }, z: 3, radius: ['6%'], center: ['50%', '30%'], label: { show: false, }, data: [ { value: 100, itemStyle: { color: '#fff', }, }, ], }, ], } return options }, generateCirclePieSeries() { const series = [] this.chartOptions.chartData.forEach((item, index) => { series.push( { name: '报警预警统计', type: 'pie', clockWise: false, //顺时加载 hoverAnimation: false, //鼠标移入变大 radius: [`${index * 10 + 3}%`, `${index * 10 + 8}%`], center: ['40%', '50%'], label: { show: false, }, itemStyle: { normal: { color: item.color ? item.color : '', }, label: { show: false, }, labelLine: { show: false, }, borderWidth: 5, }, data: [ { value: item.value, name: item.name, }, { value: 100 - item.value, name: '', itemStyle: { color: '#09295c', borderWidth: 0, }, tooltip: { show: false, }, hoverAnimation: false, }, ], }, { name: '', type: 'pie', silent: true, z: 1, clockWise: false, //顺时加载 hoverAnimation: false, //鼠标移入变大 radius: [`${index * 10 + 3}%`, `${index * 10 + 8}%`], center: ['40%', '50%'], label: { show: false, }, itemStyle: { label: { show: false, }, labelLine: { show: false, }, borderWidth: 5, }, data: [ { value: 7.5, itemStyle: { color: '#09295c', borderWidth: 0, }, tooltip: { show: false, }, hoverAnimation: false, }, ], } ) }) series.push({ name: '', type: 'pie', silent: true, z: 1, clockWise: false, //顺时加载 hoverAnimation: false, //鼠标移入变大 radius: ['96%', '97%'], center: ['40%', '50%'], label: { show: false, }, itemStyle: { label: { show: false, }, labelLine: { show: false, }, borderWidth: 5, }, data: [ { value: 7.5, itemStyle: { color: '#09295c', borderWidth: 0, }, tooltip: { show: false, }, hoverAnimation: false, }, ], }) return series }, // 生成环形图 generateCirclePieOptions() { const options = { legend: { show: true, top: 'center', left: '80%', data: this.chartOptions.chartData.map((item) => item.name), itemWidth: 14, itemHeight: 14, width: 30, padding: [0, 5], formatter: (name) => { const dataItem = this.chartOptions.chartData.find( (item) => item.name === name ) if (dataItem) { // 使用获取到的颜色设置文本标记b的颜色 return [`{title|${name}} {value|${dataItem.percent}}`] } else { return name // 如果没有找到匹配的数据项,则返回原始名称 } }, textStyle: { rich: { title: { fontSize: 12, lineHeight: 12, color: '#fff', }, value: { fontSize: 14, lineHeight: 18, color: '#fff', }, }, }, }, tooltip: { show: true, trigger: 'item', backgroundColor: '#000342', textStyle: { color: '#fff', }, }, xAxis: [ { show: false, }, ], series: this.generateCirclePieSeries(), } return options }, // 生成配置项 generateChartOption() { let options = {} if (this.chartOptions.isCustomLegend) { const customOptions = this.generateCustomOptions() options = customOptions } else if (this.chartOptions.isRoseType) { const roseTypeOptions = this.generateRoseOptions() options = roseTypeOptions } else if (this.chartOptions.isCirclePie) { const circleOptions = this.generateCirclePieOptions() options = circleOptions } else { const alignOptions = this.generateAlignOptions() options = alignOptions } this.defaultOptions = options const mergedOptions = deepMerge({}, this.defaultOptions) const res = deepMerge( mergedOptions, this.chartOptions.echartOptions || {} ) return res }, // 更新图表 updateChart() { const option = this.generateChartOption() this.chartInstance.setOption(option, true) }, }, mounted() { window.addEventListener('resize', this.handleResize) this.initChart() }, beforeDestroy() { window.removeEventListener('resize', this.handleResize) if (this.chartInstance) { this.chartInstance.dispose() } }, watch: { chartOptions: { deep: true, handler() { this.updateChart() }, }, }, }</script><!-- Add "scoped" attribute to limit CSS to this component only --><style lang="scss" scoped> .container { width: 100%; height: 100%; position: relative; .custom-legend-container { position: absolute; width: 50%; height: 200px; top: 25%; left: 50%; .custom-legend-title { font-size: 14px; position: absolute; color: #fff; top: 20%; left: 56%; } .custom-legend { position: absolute; display: flex; width: 200px; cursor: pointer; flex-direction: column; .custom-legend-info { flex: 1; display: flex; align-items: center; .name { margin-left: 10px; color: #9dcae9; font-size: 14px; } } .custom-legend-value { margin-top: 6px; padding-left: 30px; color: #fff; } } .custom-legend-1 { top: 0; left: 20px; } .custom-legend-2 { top: 0; left: 65%; } .custom-legend-3 { top: 50%; left: 20px; } .custom-legend-4 { top: 50%; left: 65%; } } .custom-subtitle { position: absolute; top: 45%; left: 12%; z-index: 50; font-size: 14px; } } #charts { width: 100%; height: 100%; margin: 0 auto; }</style> 优化代码1. 生成饼图配置项的逻辑 优化前 12345678910111213141516171819202122232425262728generateChartOption() { let options = {} if (this.chartOptions.isCustomLegend) { const customOptions = this.generateCustomOptions() options = customOptions } else if (this.chartOptions.isRoseType) { const roseTypeOptions = this.generateRoseOptions() options = roseTypeOptions } else if (this.chartOptions.isCirclePie) { const circleOptions = this.generateCirclePieOptions() options = circleOptions } else { const alignOptions = this.generateAlignOptions() options = alignOptions } this.defaultOptions = options const mergedOptions = deepMerge({}, this.defaultOptions) const res = deepMerge( mergedOptions, this.chartOptions.echartOptions || {} ) return res} 优化后 123456789101112131415161718192021generateChartOption() { let options = {} if (this.chartOptions?.isCustomLegend) { options = this.generateCustomOptions() } else if (this.chartOptions?.isRoseType) { options = this.generateRoseOptions() } else if (this.chartOptions?.isCirclePie) { options = this.generateCirclePieOptions() } else { options = this.generateAlignOptions() } this.defaultOptions = options const mergedOptions = deepMerge({}, this.defaultOptions) const res = deepMerge( mergedOptions, this.chartOptions.echartOptions || {} ) return res}