记录一下最近工作遇到的echart统计图,这个效果在官方示例那边找不到,所以记录起来,如果大家遇到有类似的需求可以参考一下。
需要的立体双柱图的效果:
难点及解决方法
1. 怎么实现这里立体效果?
可以用多个效果来叠加实现,中间的柱子就用柱状图的效果{type:bar}实现,上面和下面的椭圆形用象形柱图的效果{type:pictorialBar}实现
2. 立体效果实现了,这个背景的立体要怎么实现?
背景的立体我使用的是设置柱状图的背景,然后在再加一个最上面的椭圆来实现;
但是这里还有一个问题,这个椭圆对应在统计图上的值我要怎么拿到(我的柱状图没有设置最大值,所以这个y轴的刻度是echart自动生成的数据),这个问题只能是分成2步走,先生成统计图,在获取统计图的y轴的最大值,然后再重新生成统计图;
获取y轴最大值:chart.getModel().getComponent('yAxis').axis.scale._extent[1];
3. 只有一个柱子立体好搞,2个柱子的要怎么弄?
用象形柱图实现椭圆的最大问题是,你配置设置symbolOffset(图形相对于原本位置的偏移)为[0,椭圆高度的一半]的时候,位置是在所在的柱子的最中间的,就是以下的效果,所以设置symbolOffset的x轴的位置才是关键
那要怎么计算呢,那就得知道几个概念:
- 一个柱状图所占的width是100%
- barGap:不同系列的柱间距离,为百分比(如
'20%',表示柱子宽度的20%)。
有了这个x轴的位移就很好算了,假设我设置柱状图的barGap为'50%',那么最中间的位置是0,第一个柱子的椭圆(椭圆的中心点的位置也是0,以这个中心点来看椭圆的位移)是不是得往前移动(就用负数 - )barGap的一半('25%')和柱状图宽度的一半('50%'),也就是说我第一个柱状图的symbolOffset的x轴的位置得设置成'75%'
实现
由于得生成2遍统计图,所以最好是将配置存起来,或者弄成一个方法可以多次调用,我的实现就是封装成getOption的方法:
-
其中xAxisData给的是y轴的值,类似于['2025-03',''2025-04',''2025-05']
-
seriesData1是第一个柱状图的数据,类似于[1,41,0]这样子
-
seriesData2是第二个柱状图的数据,类似于[1,41,0]这样子
-
然后代码里面的vwToPx,和vhToPx不用管,你们可以直接设置成对应的px值,这个是我为了大屏页面才做的特殊处理,这么做就是为了统计图的字体大小之类的px值可以根据浏览器的窗口按照一定的比例进行缩小放大,我的代码里面设计图是1920*1080,所以可以看到我给宽度就是vwToPx(设计图的对应的px / 1920 * 100),给高度就是vhToPx(设计图的对应的px / 1080 * 100)
// 1. 定义 vw 转 px 的函数(1vw = 视窗宽度的 1%)
function vwToPx(vwValue) {
return (window.innerWidth * vwValue) / 100;
}
// 2. 定义 vh 转 px 的函数(如需用vh,直接调用此函数)
function vhToPx(vhValue) {
return (window.innerHeight * vhValue) / 100;
}
initSafetyManageChart(xAxisData, seriesData1, seriesData2) {
const chart = echarts.init(this.$refs.safetyManageChart);
const getOption = (maxY) => {
const option = {
backgroundColor: "transparent",
legend: {
icon: "roundRect",
right: vwToPx(20 / 1920 * 100),
itemWidth: vwToPx(14 / 1920 * 100),
itemHeight: vhToPx(8 / 1080 * 100),
selectedMode: false, // 禁止图例交互(隐藏某个图例柱状图上方和下方的圆环截面都会偏移)
data: [{
name: '问题总数',
itemStyle: {
color: '#00FFAE'
}
}, {
name: '已处理数',
itemStyle: {
color: '#00C6FF'
}
}],
textStyle: {
color: '#FFFFFF',
fontSize: vwToPx((14 / 1920) * 100)
}
},
//提示框
tooltip: {
trigger: "axis",
axisPointer: {
type: "shadow",
},
formatter: function (param) {
return `${param[0].name}<br />${param[0].marker}${param[0].seriesName} ${param[0].data}个<br />${param[1].marker}${param[1].seriesName} ${param[1].data}个`;
},
},
title: {
textAlign: 'left',
top: vhToPx((10 / 1080) * 100),
left: vwToPx((10 / 1920) * 100),
text: '单位(个)',
textStyle: {
color: '#D6EAFF',
fontSize: vwToPx((12 / 1920) * 100)
},
},
grid: {
top: vhToPx(50 / 1080 * 100),
left: vwToPx(19 / 1920 * 100),
bottom: vhToPx(6 / 1080 * 100),
right: vwToPx(20 / 1920 * 100),
containLabel: true,
},
animation: false,
xAxis: [
{
type: "category",
data: xAxisData,
axisLabel: {
color: "#D4EAFF",
fontSize: vwToPx((14 / 1920) * 100),
},
axisTick: {
show: false,
},
axisLine: {
lineStyle: {
color: "#11446F",
},
},
},
],
yAxis: [
{
show: true,
type: "value",
axisLabel: {
color: "#D4EAFF",
fontSize: vwToPx((14 / 1920) * 100),
margin: vwToPx((6 / 1920) * 100)
},
splitLine: {
lineStyle: {
color: "#11446F",
type: 'dashed',
width: vwToPx((1 / 1920) * 100)
},
},
},
],
series: [
{
name: "问题总数",
type: "bar",
barWidth: vwToPx(13 / 1920 * 100),
data: seriesData1,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#8AFCD8' },
{ offset: 1, color: '#1AA075' }
]
}
},
showBackground: true,
backgroundStyle: {
color: 'rgba(200, 255, 250, 0.2)'
}
},
{
name: "已处理数",
type: "bar",
barWidth: vwToPx(13 / 1920 * 100),
barGap: '50%', //最后一个bar的barGap生效
data: seriesData2,
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: '#00C6FF' },
{ offset: 1, color: '#1173B8' }
]
}
},
showBackground: true,
backgroundStyle: {
color: 'rgba(177, 215, 255, 0.2)'
}
},
// 顶部的椭圆形(象形柱图):pictorialBar
{
name: "问题总数",
type: "pictorialBar",
symbolSize: [vwToPx(13 / 1920 * 100), vhToPx(5 / 1080 * 100)],
symbolOffset: ['-75%', -vhToPx(2.5 / 1080 * 100)],
z: 12,
symbolPosition: "end",
itemStyle: {
color: '#C8FFFA'
},
data: seriesData1
},
// 顶部的椭圆形(象形柱图):pictorialBar
{
name: "已处理数",
type: "pictorialBar",
symbolSize: [vwToPx(13 / 1920 * 100), vhToPx(5 / 1080 * 100)],
symbolOffset: ['75%', -vhToPx(2.5 / 1080 * 100)],
z: 12,
symbolPosition: "end",
itemStyle: {
color: '#B1D7FF'
},
data: seriesData2
},
],
};
if (maxY) {
const maxYList = new Array(seriesData1.length).fill(maxY)
option.series = [
...option.series,
// 顶部的椭圆形(象形柱图):pictorialBar
{
name: "问题总数",
type: "pictorialBar",
symbolSize: [vwToPx(13 / 1920 * 100), vhToPx(5 / 1080 * 100)],
symbolOffset: ['-75%', -vhToPx(2.5 / 1080 * 100)],
z: 10,
symbolPosition: "end",
itemStyle: {
color: 'rgba(200, 255, 250, 0.3)'
},
data: maxYList
},
// 顶部的椭圆形(象形柱图):pictorialBar
{
name: "已处理数",
type: "pictorialBar",
symbolSize: [vwToPx(13 / 1920 * 100), vhToPx(5 / 1080 * 100)],
symbolOffset: ['75%', -vhToPx(2.5 / 1080 * 100)],
z: 10,
symbolPosition: "end",
itemStyle: {
color: 'rgba(177, 215, 255, 0.3)'
},
data: maxYList
},
]
}
return option;
}
chart.setOption(getOption());
//获取Y轴的最大值
var maxY = chart.getModel().getComponent('yAxis').axis.scale._extent[1];
chart.setOption(getOption(maxY));
window.addEventListener('resize', () => {
chart.setOption(getOption(maxY))
chart.resize()
})
},