目的:
需在柱状图中展示每个月份的数据增长率,如下图效果:
实现:
使用到echarts中的custom series,通过编写renderItem来实现相邻两个数据的增长率曲线,该图表有两个custom series实现。
完整代码如下:
<template>
<div class="chart-bar" ref="chartRef"></div>
</template>
<script setup lang="ts">
import type { Ref } from 'vue';
import echarts from '/@/utils/echarts';
import { useECharts } from '/@/hooks/useECharts';
const props = defineProps({
loading: Boolean,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
const heatCount = 3; // 供暖方式数量
const categoryCount = 5; //x轴展示数目
const xAxisData: string[] = ['11月', '12月', '1月', '2月', '3月']; // x轴数据
const customData: number[][] = []; // 同比增长率数据
let legendData: string[] = []; // 图示数据
const dataList: number[][] = []; // 柱状图数据
let color = ['rgba(7, 147, 162', 'rgba(133, 123, 96', 'rgba(16, 126, 77'];
legendData.push('同比增长率');
legendData = legendData.concat(['数据1', '数据2', '数据3']);
const encodeY = []; // 定义映射到y轴的数据维度
for (let i = 0; i < heatCount; i++) {
// legendData.push(2010 + i + '');
dataList.push([]);
encodeY.push(1 + i);
}
for (let i = 0; i < categoryCount; i++) {
let val = Math.random() * 200;
// xAxisData.push('category' + i);
let customVal = [i];
customData.push(customVal);
for (let j = 0; j < dataList.length; j++) {
let value =
j === 0
? echarts.number.round(val, 2)
: echarts.number.round(Math.max(0, dataList[j - 1][i] + Math.random() * 100), 2);
dataList[j].push(value);
customVal.push(value);
}
}
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
show: true,
trigger: 'item',
textStyle: {
color: '#fff',
},
backgroundColor: 'rgba(0,0,0,0.2)', // 背景
padding: [8, 10], //内边距
extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影
formatter: function (params) {
if (params.componentSubType !== 'custom')
return params.name + '<br>' + params.seriesName + ' ' + params.value + ' kW';
else return '';
},
},
grid: {
left: '5%',
top: '30%',
right: '5%',
bottom: '1%',
containLabel: true,
},
legend: [
{
data: ['数据1', '数据2', '数据3'],
top: 10,
right: 10,
itemHeight: 10,
itemWidth: 20,
textStyle: {
color: '#fff',
},
},
{
data: ['同比增长率'],
top: 35,
right: 10,
icon: 'rect',
itemWidth: 16,
itemHeight: 4,
textStyle: {
color: '#fff',
},
},
],
xAxis: {
type: 'category',
// data: data.map((n) => n.time),
axisLine: {
lineStyle: {
color: '#18649e',
width: 2,
},
},
axisLabel: {
// rotate: 40,
margin: 16,
color: '#fff',
},
axisTick: {
show: false,
},
data: xAxisData,
},
yAxis: {
type: 'value',
name: '万元',
min: 0,
// max: 150,
nameTextStyle: {
color: '#fff',
verticalAlign: 'middle',
},
axisLabel: {
// rotate: 40,
margin: 16,
color: '#fff',
},
axisLine: {
show: true,
lineStyle: {
color: '#18649e',
width: 2,
},
},
splitLine: {
lineStyle: {
color: '#224180',
type: 'dashed',
},
},
},
series: [
{
type: 'custom', // custom 系列需要开发者自己提供图形渲染的逻辑
name: '同比增长率',
color: 'rgba(0, 255, 255)',
// 绘制一个x轴坐标点的数据
renderItem: function (params, api) {
// params:包含了当前数据信息和坐标系的信息。 api:是一些开发者可调用的方法集合。
let xValue = api.value(0); // 表示取出当前 dataItem 中第一个维度的数值。api.value(0) 为x轴数据
let currentSeriesIndices = [1, 2]; // 前两组数
let barLayout = api.barLayout({
// 在柱状图上附加信息时,使用barLayout方法得到layout信息
barGap: '30%',
barCategoryGap: '20%',
count: 3,
});
let points = [];
// 绘制前两组数据增长率 params.seriesIndex:0,
for (let i = 0; i < currentSeriesIndices.length; i++) {
let seriesIndex = currentSeriesIndices[i];
// debugger;
// if (seriesIndex !== params.seriesIndex) {
let point = api.coord([xValue, api.value(seriesIndex)]);
point[0] += barLayout[i].offsetCenter; // 数据点x轴位置
point[1] -= 10; // 数据点y轴位置
points.push(point);
// }
}
let style = api.style({
stroke: api.visual('color') as string,
fill: 'none',
lineJoin: 'bevel',
});
return {
type: 'polyline',
shape: {
points: points,
},
style: style,
};
},
label: {
show: true,
position: 'middle',
formatter: (params) => {
return `${(((params.data[2] - params.data[1]) / params.data[1]) * 100).toFixed(
1,
)}%`;
},
},
labelLayout: function (params) {
return {
x: params.rect.x - 10,
y: params.rect.y - 10,
verticalAlign: 'middle',
align: 'left',
};
},
itemStyle: {
borderWidth: 2,
},
encode: {
x: 0, // 可以定义 data 的哪个维度被编码成什么, data的每一列称为一个『维度』。这里维度0映射到x轴,维度encodeY映射到y轴
y: encodeY,
},
data: customData,
// data: arrYLabel2,
z: 100,
},
{
type: 'custom', // custom 系列需要开发者自己提供图形渲染的逻辑
name: '同比增长率2',
color: 'rgba(0, 255, 255)',
renderItem: function (params, api) {
// params:包含了当前数据信息和坐标系的信息。 api:是一些开发者可调用的方法集合。
let xValue = api.value(0); // 表示取出当前 dataItem 中第一个维度的数值。
// let currentSeriesIndices = api.currentSeriesIndices(); // 得到series的当前index [0,1,2,3]
let currentSeriesIndices = [2, 3]; // 后两组数
let barLayout = api.barLayout({
// 在柱状图上附加信息时,使用barLayout方法得到layout信息
barGap: '30%',
barCategoryGap: '20%',
count: 3,
});
let points = [];
for (let i = 0; i < currentSeriesIndices.length; i++) {
let seriesIndex = currentSeriesIndices[i];
let point = api.coord([xValue, api.value(seriesIndex)]);
point[0] += barLayout[i + 1].offsetCenter;
point[1] -= 10;
points.push(point);
}
let style = api.style({
stroke: api.visual('color') as string,
fill: 'none',
lineJoin: 'bevel',
});
return {
type: 'polyline',
shape: {
points: points,
},
style: style,
};
},
label: {
show: true,
position: 'middle',
formatter: (params) => {
return `${(((params.data[3] - params.data[2]) / params.data[2]) * 100).toFixed(
1,
)}%`;
},
},
labelLayout: function (params) {
return {
x: params.rect.x + 10,
y: params.rect.y - 5,
verticalAlign: 'middle',
align: 'left',
};
},
itemStyle: {
borderWidth: 2,
},
encode: {
x: 0, // 可以定义 data 的哪个维度被编码成什么, data的每一列称为一个『维度』。这里维度0映射到x轴,维度encodeY映射到y轴
y: encodeY,
},
data: customData,
// data: arrYLabel2,
z: 100,
},
...dataList.map(function (data, index) {
return {
type: 'bar',
animation: false,
name: legendData[index + 1],
color: color[index] + ')',
data: data,
} as echarts.BarSeriesOption;
}),
],
});
},
{ immediate: true },
);
</script>
<style lang="less" scoped>
.chart-bar {
width: 100%;
height: 200px;
// margin-top: 30px;
}
</style>