封装一个立体柱状图组件 发表于 2024-07-13 最近因为工作需要,对 echarts 进行了一些封装,正好借此机会熟悉了一下 echarts 的各种配置项。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532<template> <div id="charts" ref="chartContainer"></div></template><script> import * as echarts from 'echarts' import { hexToRgb, deepMerge } from '@/utils/index.js' export default { name: 'CgStereoBarChart', data() { return { defaultOptions: {}, chartInstance: null, transparency: 0.3, xAxisData: [ '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', '00', ], colors: [ '#fffa65', '#ffa502', '#c0392b', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', ], } }, props: { chartOptions: { type: Object, default: () => ({ xAxisName: '区/县', yAxisName: '总数', xAxisData: [], chartData: [], barWidth: 18, maxValue: 20, echartOptions: {}, colors: [ '#fffa65', '#ffa502', '#c0392b', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff', ], backgroundColor: '#010d3a', }), }, }, methods: { handleResize() { this.$nextTick(() => { if (this.chartInstance) { this.chartInstance.resize() } }) }, // 初始化echarts initChart() { const chartContainer = this.$refs.chartContainer this.chartInstance = echarts.init(chartContainer) this.updateChart() }, registerShape() { const barWidth = this.chartOptions.barWidth || 18 const CubeLeft = echarts.graphic.extendShape({ shape: { x: 0, y: 0, }, buildPath: (ctx, shape) => { const xAxisPoint = shape.xAxisPoint const c0 = [shape.x, shape.y] const c1 = [shape.x - barWidth / 2, shape.y - barWidth / 2] const c2 = [ xAxisPoint[0] - barWidth / 2, xAxisPoint[1] - barWidth / 2, ] const c3 = [xAxisPoint[0], xAxisPoint[1]] ctx .moveTo(c0[0], c0[1]) .lineTo(c1[0], c1[1]) .lineTo(c2[0], c2[1]) .lineTo(c3[0], c3[1]) .closePath() }, }) // 定义柱状图右侧图形元素 const CubeRight = echarts.graphic.extendShape({ shape: { x: 0, y: 0, }, buildPath: (ctx, shape) => { const xAxisPoint = shape.xAxisPoint const c1 = [shape.x, shape.y] const c2 = [xAxisPoint[0], xAxisPoint[1]] const c3 = [xAxisPoint[0] + barWidth, xAxisPoint[1] - barWidth / 2] const c4 = [shape.x + barWidth, shape.y - barWidth / 2] ctx .moveTo(c1[0], c1[1]) .lineTo(c2[0], c2[1]) .lineTo(c3[0], c3[1]) .lineTo(c4[0], c4[1]) .closePath() }, }) // 定义柱状图顶部图形元素 const CubeTop = echarts.graphic.extendShape({ shape: { x: 0, y: 0, }, buildPath: (ctx, shape) => { const c1 = [shape.x, shape.y] const c2 = [shape.x + barWidth, shape.y - barWidth / 2] const c3 = [shape.x + barWidth / 2, shape.y - barWidth] const c4 = [shape.x - barWidth / 2, shape.y - barWidth / 2] ctx .moveTo(c1[0], c1[1]) .lineTo(c2[0], c2[1]) .lineTo(c3[0], c3[1]) .lineTo(c4[0], c4[1]) .closePath() }, }) echarts.graphic.registerShape('CubeLeft', CubeLeft) echarts.graphic.registerShape('CubeRight', CubeRight) echarts.graphic.registerShape('CubeTop', CubeTop) }, // 生成配置项 generateChartOption() { const colors = this.chartOptions.colors ? this.chartOptions.colors : this.colors this.defaultOptions = { backgroundColor: this.chartOptions.backgroundColor || '#010d3a', legend: { top: 20, itemGap: 60, data: this.chartOptions.chartData.map((item, index) => { return { name: item.name, icon: 'roundRect', // 可以是 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' 之一 textStyle: { color: '#fff', // 图例文字的样式 }, // 显式设置图例颜色 itemStyle: { color: colors[index], // 这里设置为你柱子的实际颜色 }, } }), }, title: { text: '', top: 10, left: 10, textStyle: { color: '#00F6FF', fontSize: 24, }, }, grid: { left: 40, right: 60, bottom: '10%', top: 80, containLabel: true, }, tooltip: { show: true, trigger: 'axis', backgroundColor: '#000342', textStyle: { color: '#fff', }, }, xAxis: { type: 'category', name: this.chartOptions.xAxisName || '区/县', data: this.chartOptions.xAxisData || this.xAxisData, axisLine: { show: true, lineStyle: { color: '#1d97cf', }, }, axisTick: { show: true, length: 9, alignWithLabel: true, lineStyle: { color: '#1d97cf', }, }, axisLabel: { fontSize: 14, margin: 20, }, }, yAxis: { type: 'value', name: this.chartOptions.yAxisName ? this.chartOptions.yAxisName + ' ' : '总数' + ' ', max: this.chartOptions.maxValue || 20, axisLine: { show: true, lineStyle: { color: '#1d97cf', }, }, splitLine: { show: true, lineStyle: { color: '#153d7d', type: 'dashed', }, }, axisTick: { show: true, }, axisLabel: { fontSize: 12, formatter: function (value, index) { // 检查value是否是数字 if (typeof value === 'number') { // 格式化为两位数,如果是一位数则在前面补零 return (value < 10 ? '0' : '') + value } // 如果value不是数字,则直接返回原始值 return value }, }, }, series: this.generateSeriesConfig(), } const mergedOptions = deepMerge({}, this.defaultOptions) const res = deepMerge( mergedOptions, this.chartOptions.echartOptions || {} ) return res }, // 生成series的配置项 generateSeriesConfig() { const barWidth = this.chartOptions.barWidth || 18 const colors = this.chartOptions.colors ? this.chartOptions.colors : this.colors const series = [] const widthRatio = barWidth / 18 const barGap = 14 * widthRatio // 设置柱子之间的间距 const originalAdditionalOffset = -14 * 2 // 原始的额外间距调整 const newAdditionalOffset = originalAdditionalOffset * widthRatio this.chartOptions.chartData.forEach((item, index) => { series.push( { name: item.name, type: 'custom', tooltip: { show: false, }, renderItem: (params, api) => { const offset = index * barGap * 2 const location = api.coord([api.value(0), api.value(1)]) location[0] += newAdditionalOffset + offset const xlocation = api.coord([api.value(0), 0]) xlocation[0] += newAdditionalOffset + offset const baseShape = { api, xValue: api.value(0), yValue: api.value(1), x: location[0], y: location[1], xAxisPoint: xlocation, } const cubeShapes = ['CubeLeft', 'CubeRight', 'CubeTop'].map( (type) => ({ type, shape: { ...baseShape }, style: { fill: item.color ? `${hexToRgb(item.color, this.transparency)}` : `${hexToRgb(colors[index], this.transparency)}`, }, }) ) return { type: 'group', children: cubeShapes, } }, data: Array.from( { length: item.value.length }, (_) => this.chartOptions.maxValue || 20 ), }, { name: item.name, type: 'custom', renderItem: (params, api) => { const offset = index * barGap * 2 const location = api.coord([api.value(0), api.value(1)]) location[0] += newAdditionalOffset + offset const xlocation = api.coord([api.value(0), 0]) xlocation[0] += newAdditionalOffset + offset const createLinearGradient = (color1, color2) => { return new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: color1 }, { offset: 1, color: color2 }, ]) } const baseShape = { api, xValue: api.value(0), yValue: api.value(1), x: location[0], y: location[1], xAxisPoint: xlocation, } const cubeParts = [ { type: 'CubeLeft', style: { fill: createLinearGradient( item.color ? item.color : colors[index], item.color ? item.color : colors[index] ), }, }, { type: 'CubeRight', style: { fill: createLinearGradient( item.color ? item.color : colors[index], item.color ? item.color : colors[index] ), }, }, { type: 'CubeTop', style: { fill: createLinearGradient( item.color ? item.color : colors[index], item.color ? item.color : colors[index] ), }, }, ] return { type: 'group', children: cubeParts.map((part) => ({ ...part, shape: { ...baseShape }, })), } }, color: item.color ? item.color : colors[index], data: item.value, } ) }) return series }, // 更新图表 updateChart() { const option = this.generateChartOption() this.chartInstance.setOption(option, true) }, }, mounted() { // 页面加载完毕初始化图表 this.registerShape() // 定义柱状图左侧图形元素 this.initChart() window.addEventListener('resize', this.handleResize) }, beforeDestroy() { window.removeEventListener('resize', this.handleResize) if (this.chartInstance) { this.chartInstance.dispose() } }, watch: { 'chartOptions.echartOptions': { deep: true, handler(newValue) { this.updateChart() }, }, 'chartOptions.maxValue': { handler() { this.registerShape() this.updateChart() }, }, 'chartOptions.colors': { deep: true, handler() { this.updateChart() }, }, // 侦听chartData 'chartOptions.chartData': { deep: true, handler() { this.updateChart() }, }, 'chartOptions.barWidth': { handler(newValue) { const CubeLeft = echarts.graphic.extendShape({ shape: { x: 0, y: 0, }, buildPath: (ctx, shape) => { const xAxisPoint = shape.xAxisPoint const c0 = [shape.x, shape.y + this.chartOptions.barWidth] const c1 = [ shape.x - newValue / 2, shape.y + this.chartOptions.barWidth - newValue / 2, ] const c2 = [ xAxisPoint[0] - newValue / 2, xAxisPoint[1] - newValue / 2, ] const c3 = [xAxisPoint[0], xAxisPoint[1]] ctx .moveTo(c0[0], c0[1]) .lineTo(c1[0], c1[1]) .lineTo(c2[0], c2[1]) .lineTo(c3[0], c3[1]) .closePath() }, }) // 定义柱状图右侧图形元素 const CubeRight = echarts.graphic.extendShape({ shape: { x: 0, y: 0, }, buildPath: (ctx, shape) => { const xAxisPoint = shape.xAxisPoint const c1 = [shape.x, shape.y + this.chartOptions.barWidth] const c2 = [xAxisPoint[0], xAxisPoint[1]] const c3 = [ xAxisPoint[0] + newValue, xAxisPoint[1] - newValue / 2, ] const c4 = [ shape.x + newValue, shape.y + this.chartOptions.barWidth - newValue / 2, ] ctx .moveTo(c1[0], c1[1]) .lineTo(c2[0], c2[1]) .lineTo(c3[0], c3[1]) .lineTo(c4[0], c4[1]) .closePath() }, }) // 定义柱状图顶部图形元素 const CubeTop = echarts.graphic.extendShape({ shape: { x: 0, y: 0, }, buildPath: (ctx, shape) => { const c1 = [shape.x, shape.y + this.chartOptions.barWidth] const c2 = [ shape.x + newValue, shape.y + this.chartOptions.barWidth - newValue / 2, ] const c3 = [ shape.x + newValue / 2, shape.y + this.chartOptions.barWidth - newValue, ] const c4 = [ shape.x - newValue / 2, shape.y + this.chartOptions.barWidth - newValue / 2, ] ctx .moveTo(c1[0], c1[1]) .lineTo(c2[0], c2[1]) .lineTo(c3[0], c3[1]) .lineTo(c4[0], c4[1]) .closePath() }, }) echarts.graphic.registerShape('CubeLeft', CubeLeft) echarts.graphic.registerShape('CubeRight', CubeRight) echarts.graphic.registerShape('CubeTop', CubeTop) this.updateChart() }, }, }, }</script><!-- Add "scoped" attribute to limit CSS to this component only --><style scoped> #charts { width: 100%; height: 100%; margin: 0 auto; }</style>