实现效果图如下:
上面除了立体图之外还增加了背景图。注意,可以发现这个图的右下角是是和x轴平齐的,如果右下角也要折角,可以根据代码修改下描点的点位就可以了。
完整代码如下:
<template>
<div id="bar-chart" ref="barChartRef"></div>
</template>
<script setup>
import { ref, onMounted, nextTick } from 'vue';
import * as echarts from 'echarts'
const barChartRef = ref()
let barChart = null
let options = {}
const data = ref([])
const imgSrc =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAI6CAYAAACw4c+4AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo0NWI1MDY3Mi1kMzI4LTNmNDQtOTA2NC0xNDhlOGFlNWUzYmIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzI5MUNFMEQyOUJCMTFFRTg4ODhBOEM3M0UzMDIwQUMiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzI5MUNFMEMyOUJCMTFFRTg4ODhBOEM3M0UzMDIwQUMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NWExOTdhNTctNDIzYi01MDQyLThkMGMtZjMxZTIwNWRlYTViIiBzdFJlZjpkb2N1bWVudElEPSJhZG9iZTpkb2NpZDpwaG90b3Nob3A6NjE2MTU1ZmUtY2M3Ni1mNTQ3LThlNzItMmZkOTcxZmU0Mjk5Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+hTdV3gAABDNJREFUeNrs1iESQAAURVG+JFqCGV0QVNuwZEGwEkk1qhmSH5Tz6m0vnbLtp7m4bRz6Zlm3vXiZ9mzhhFwLJ+RaOCHXwgm5Vh1n3Tnoewsn5Fo4AWMwBmMwxkEYgzEYgzEOwhiMwRiMcRDGYAzGYIyDMAZjMAZjNIzBGIzBGA1jMAZjMEbDGIzBGIzRMAZjMAZjNIzBGIzBGA1jMAZjMAZjHIQxGIMxGOMgjMEYjMEYB2EMxmAMxjgIYzAGYzDGQRiDMRiDMRrGYAzGYIyGMRiDMRijYQzGYAzGaBiDMRiDMRrGYAzGYAzGOAFjMAZjMMZBGIMxGIMxDsIYjMEYjHEQxmAMxmCMgzAGYzAGYzSMwRiMwRgNYzAGYzBGwxiMwRiM0TAGYzAGYzSMwRiMwRgNYzAGYzAGYzAGYzAGYzDGQRiDMRiDMQ7CGIzBGIxxEMZgDMZgjIMwBmMwBmM0jMEYjMEYDWMwBmMwRsMYjMEYjNEwBmMwBmM0jMEYjMEYjHECxmAMxmCMgzAGYzAGYxyEMRiDMRjjIIzBGIzBGAdhDMZgDMZoGIMxGIMxGsZgDMZgjIYxGIMxGKNhDMZgDMZoGIMxGIMxGsZgDMZgDMY4CGMwBmMwxkEYgzEYgzEOwhiMwRiMcRDGYAzGYIyDMAZjMAZjNIzBGIzBGA1jMAZjMEbDGIzBGIzRMAZjMAZjNIzBGIzBGIxxAsZgDMZgjIMwBmMwBmMchDEYgzEY4yCMwRiMwRgHYQzGYAzGOAhjMAZjMEbDGIzBGIzRMAZjMAZjNIzBGIzBGA1jMAZjMEbDGIzBGIzBGAdhDMZgDMY4CGMwBmMwxkEYgzEYgzEOwhiMwRiMcRDGYAzGYIyGMRiDMRijYQzGYAzGaBiDMRiDMRrGYAzGYIyGMRiDMRiDMU7AGIzBGIxxEMZgDMZgjIMwBmMwBmMchDEYgzEY4yCMwRiMwRgNYzAGYzBGwxiMwRiM0TAGYzAGYzSMwRiMwRgNYzAGYzBGwxiMwRiMwRgHYQzGYAzGOAhjMAZjMMZBGIMxGIMxDsIYjMEYjHEQxmAMxmCMhjEYgzEYo2EMxmAMxmgYgzEYgzEaxmAMxmCMhjEYgzEYgzFOwBiMwRiMcRDGYAzGYIyDMAZjMAZjHIQxGIMxGOMgjMEYjMEYDWMwBmMwRsMYjMEYjNEwBmMwBmM0jMEYjMEYDWMwBmMwRsMYjMEYjMEYB2EMxmAMxjgIYzAGYzDGQRiDMRiDMQ7CGIzBGIxxEMZgDMZgjIYxGIMxGKNhDMZgDMZoGIMxGIMxGsZgDMZgjIYxGIMxGIMxGIMxGIMxGOMgjMEYjMEYB2EMxmAMxjgIYzAGYzDGQRiDMRiDMRrGYAzGYIyGMRiDMRijYQzGYAzGaBiDMRiDMRrGYAzGYIyGMRiDMRiDMQ7CmP/aJcAAaiRuLcLv/0cAAAAASUVORK5CYII='
const patternImg = new Image()
patternImg.src = imgSrc
const initChart = async () => {
if (barChart) {
barChart.dispose()
barChart = null
}
await nextTick()
barChart = echarts.init(barChartRef.value)
// 数据
data.value = [
{ name: '西瓜', value: 10 },
{ name: '草莓', value: 20 },
{ name: '苹果', value: 15 },
{ name: '菠萝', value: 28 },
{ name: '葡萄', value: 13 }
]
const xData = data.value.map(item => item.name) // x轴的label数据
const yData = data.value.map(item => item.value) // series中data的数据
options = {
tooltip: {
trigger: 'axis',
},
grid: {
top: 20,
left: 30,
right: 10,
bottom: 30
},
xAxis: {
type: 'category',
data: xData,
// 坐标轴线
axisLine: {
show: true,
lineStyle: {
color: '#FFFFFF',
opacity: 0.4,
width: 1,
type: 'solid'
}
},
// 坐标轴刻度
axisTick: {
show: false
},
// 坐标轴刻度标签
axisLabel: {
show: true,
color: '#FFFFFF',
fontWeight: 400,
fontSize: 12,
margin: 12
},
// 分隔线
splitLine: {
show: true,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.15,
type: 'dashed'
}
}
},
yAxis: {
type: 'value',
// max: 1000,
// 坐标轴名称和样式
name: '',
minInterval: 1,
nameTextStyle: {
padding: [0, 8, 0, 0],
fontSize: 12,
color: '#8F9297',
fontWeight: 400
},
// 坐标轴线
axisLine: {
show: true,
lineStyle: {
color: '#FFFFFF',
opacity: 0.14,
width: 1
}
},
// 坐标轴刻度
axisTick: {
show: false
},
// 坐标轴刻度标签
axisLabel: {
show: true,
color: '#8F9297',
fontSize: 12,
fontWeight: 400,
},
// 分隔线
splitLine: {
show: true,
lineStyle: {
color: '#FFFFFF',
width: 1,
opacity: 0.15,
type: 'dashed'
}
}
},
series: [
{
data: yData,
type: 'custom',
renderItem: function (params, api) {
return getRenderItem(params, api)
},
itemStyle: {
color: {
image: patternImg,
repeat: 'repeat'
}
}
}
]
}
// 定义柱状图左侧图形元素
const leftRect = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0,
width: 21, //柱状图宽
},
buildPath: function (ctx, shape) {
const api = shape.api
const xAxisPoint = api.coord([shape.xValue, 0]) // 根据列表中index的值转化为坐标点
const p0 = [shape.x - shape.width, shape.y] // 左上点位
const p1 = [shape.x - shape.width, xAxisPoint[1]] // 左下点位
const p2 = [shape.x, xAxisPoint[1]] // 右下点位
const p3 = [shape.x, shape.y] // 右上点位
ctx.moveTo(p0[0], p0[1])
ctx.lineTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p0[0], p0[1])
ctx.closePath()
}
})
// 定义柱状图右侧图形元素
const rightRect = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0,
width: 11,
zWidth: 11,
zHeight: 11
},
buildPath: function (ctx, shape) {
const api = shape.api
const xAxisPoint = api.coord([shape.xValue, 0]) // 坐标点
const p0 = [shape.x, shape.y] // 左上点位
const p1 = [shape.x, xAxisPoint[1]] // 左下点位
const p2 = [shape.x + shape.width, xAxisPoint[1]] // 右下点位
const p3 = [shape.x + shape.width, shape.y - shape.zHeight] // 右上点位
ctx.moveTo(p0[0], p0[1])
ctx.lineTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p0[0], p0[1])
ctx.closePath()
}
})
// 定义柱状图顶部图形元素
const topRect = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0,
width: 21,
zWidth: 11,
zHeight: 11
},
buildPath: function (ctx, shape) {
const p0 = [shape.x - shape.width + shape.zWidth, shape.y - shape.zHeight] // 左上点位
const p1 = [shape.x - shape.width, shape.y] // 左下点位
const p2 = [shape.x, shape.y] // 右下点位
const p3 = [shape.x + shape.zWidth, shape.y - shape.zHeight] // 右上点位
ctx.moveTo(p0[0], p0[1])
ctx.lineTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p0[0], p0[1])
ctx.closePath()
}
})
// 定义柱状图背景图形元素
const bgRect = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0,
width: 32,
zWidth: 11,
zHeight: 0
},
buildPath: function (ctx, shape) {
const api = shape.api
const xAxisPoint = api.coord([shape.xValue, 0]) // 坐标点
const p0 = [shape.x - shape.width + shape.zWidth, 20] // 左上点位 根据grid.top的位置得到高
const p1 = [shape.x - shape.width + shape.zWidth, xAxisPoint[1]] // 左下点位
const p2 = [shape.x + shape.zWidth, xAxisPoint[1]] // 右下点位
const p3 = [shape.x + shape.zWidth, 20] // 右上点位 根据grid.top的位置得到高
ctx.moveTo(p0[0], p0[1])
ctx.lineTo(p1[0], p1[1])
ctx.lineTo(p2[0], p2[1])
ctx.lineTo(p3[0], p3[1])
ctx.lineTo(p0[0], p0[1])
ctx.closePath()
}
})
// 注册图形元素
echarts.graphic.registerShape('leftRect', leftRect)
echarts.graphic.registerShape('rightRect', rightRect)
echarts.graphic.registerShape('topRect', topRect)
echarts.graphic.registerShape('bgRect', bgRect)
// 渲染图形
function getRenderItem(params, api) {
const location = api.coord([api.value(0), api.value(1)]) // 根据 data的index值和data转化为坐标像数值
return {
type: 'group',
children: [
{
type: 'bgRect',
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: location[0],
y: location[1]
},
style: api.style()
},
{
type: 'leftRect',
shape: {
api,
xValue: api.value(0), // index值
yValue: api.value(1), // data值
x: location[0], // x像素
y: location[1] // y像素
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 1, color: 'rgba(57,160,247,0)' },
{ offset: 0, color: 'rgba(57, 159, 246,1)' }
])
}
},
{
type: 'rightRect',
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: location[0],
y: location[1]
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 1, color: 'rgba(32,130,213,0)' },
{ offset: 0, color: 'rgba(32,130,213,1)' }
])
}
},
{
type: 'topRect',
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: location[0],
y: location[1]
},
style: {
fill: '#3392E3'
}
}
]
}
}
options && barChart.setOption(options, true)
}
onMounted(() => {
initChart()
})
</script>
<style lang="scss" scoped>
#bar-chart {
width: 500px;
height: 400px;
background-color: #142331;
}
</style>
这里最关键的实现思路就是把各个点位计算出来,然后连线。如果实在不懂这个意思的话建议是了解下canvas绘画或者仔细阅读下echarts中custom的自定义图表。最简单就是打印一下getRenderItem(params, api)
中的参数代表什么意思。canvas的图都是从左上角(0,0)为起点。
欢迎评论。