Echarts+Angular 阶梯瀑布图实现跨象限算法
- 用于财务系统Dashboard展示
- 算法的代码片段
- 效果展示
- 引用
用于财务系统Dashboard展示
由于echart瀑布图不是一个封装好的模型,不像Line和Bar一样可以直接使用,它需要一系列的跨象限的计算和处理。在我的实际需求中,每个坐标的展示颜色要根据固定的类型去调整。有的是正数为红色,有的是负数为红色,(因为在财务系统中,有的正数为好,有的负数为好,所以展示的颜色要根据实际需求)且,实际展示时,还通过自定义的图形悬浮在图标的上方。以下是我结合chatgpt进行调整后的样式,欢迎大家帮忙给出更好的解决方案。
算法的代码片段
getdata(datas: any) {
const dataSum: number[] = []; // 透明项的data数据
const dataIn1: Array<number | string> = []; // 增加上半段的data数据 (跨象限时)
const dataIn2: Array<number | string> = []; // 增加下半段的data数据 (跨象限时)
const dataOut1: Array<number | string> = []; // 减少上半段的data数据 (跨象限时)
const dataOut2: Array<number | string> = []; // 减少下半段的data数据 (跨象限时)
const setSumIndex: number[] = []; // 有跨象限时值的坐标
const len: number = datas.length;
// 组装数据
datas.forEach((item: number, index: number) => {
if (index === 0) {
dataSum.push(0); //第一个柱子的辅助项为0
} else if (index < len - 1) {
let num = 0;
for (let i = 0; i < index; i++) {
num = num + datas[i]; //下一个柱子的辅助项是前面数据的总和加上自己本身
}
if ((item >= 0 && num >= 0) || (item < 0 && num <= 0)) {
//不需要跨象限的情况
dataSum.push(num);
} else {
//需要跨象限
num = num + item;
dataSum.push(num);
}
} else {
//最后一个柱子不需要辅助项,因为就是展示第一个柱子到最后一个的变化趋势
dataSum.push(0);
}
//最后一个柱子的值直接放
if (index === len - 1) {
if (item >= 0) {
dataIn1.push(item);
dataIn2.push('-');
dataOut1.push('-');
dataOut2.push('-');
} else {
dataIn1.push('-');
dataIn2.push('-');
dataOut1.push(item);
dataOut2.push('-');
}
}
//其他柱子的设置 1.当值为正时
else if (item >= 0) {
// 当前起始位置
let beginNum = 0;
for (let i = 0; i < index; i++) {
beginNum = beginNum + datas[i];
}
if (index === 0 || beginNum >= 0) {
dataIn1.push(item);
dataIn2.push('-');
} else if (beginNum < 0) {
if (Math.abs(beginNum) === item) {
dataIn1.push(item);
dataIn2.push('-');
} else if (Math.abs(beginNum) > item) {
dataIn1.push(item * -1);
dataIn2.push('-');
} else {
dataIn1.push(item + beginNum);
dataIn2.push(beginNum);
setSumIndex.push(index);
}
}
dataOut1.push('-');
dataOut2.push('-');
}
//当值为负时
else if (item < 0) {
dataIn1.push('-');
dataIn2.push('-');
// 当前起始位置
let beginNum = 0;
for (let i = 0; i < index; i++) {
beginNum = beginNum + datas[i];
}
if (index === 0 || beginNum <= 0) {
dataOut1.push(item);
dataOut2.push('-');
} else if (beginNum > 0) {
if (beginNum >= Math.abs(item)) {
dataOut1.push(item * -1);
dataOut2.push('-');
} else {
dataOut1.push(item + beginNum);
dataOut2.push(beginNum);
setSumIndex.push(index);
}
}
}
});
// 跨分区的为0
setSumIndex.forEach(item => {
dataSum[item] = 0;
});
this.dataSum = dataSum;
this.dataIn1 = dataIn1;
this.dataIn2 = dataIn2;
this.dataOut1 = dataOut1;
this.dataOut2 = dataOut2;
}
echart Option代码,其中Actual和Locked项位于柱状图首位,中间的柱子上下的浮动变化是为了展示从Locked的值到Actual的值,的一个值的演变过程。
percentwaterfallChart() {
this.rankingBarOption = {
title: {
text: ''
},
tooltip: {
show: false
},
toolbox: {
feature: {
saveAsImage: {}
}
},
grid: {
top: '50px',
left: '20px',
right: '50px',
bottom: '0%',
containLabel: true
},
xAxis: {
type: 'category',
data: this.pcdata.pc.map((o: any) => o),
axisLine: {
onZero: false
},
axisTick: {
show: false
},
axisLabel: {
interval: 0,
formatter: (value: string, index: number) => {
if (index % 2 === 0) {
return `{a|${value}}`;
} else {
return `{b|${value}}`;
}
},
rich: {
a: {
lineHeight: 20,
align: 'center'
},
b: {
lineHeight: 20,
align: 'center',
padding: [20, 0, 0, 0]
}
}
}
},
yAxis: {
type: 'value',
splitLine: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#000'
}
},
axisLabel: {
formatter: (value: string, index: number) => {
if (this.currentAccount == 'CD') {
return Number(value) == 0 ? `0.00` : Number(value).toFixed(2); //用于展示保留两位小数
}
return Number(value) == 0 ? `0.00%` : `${(Number(value) * 100).toFixed(2)}%`; //用于展示百分比的展示样式
}
}
},
series: [
{
name: '辅助',
type: 'bar',
stack: '总量',
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
},
emphasis: {
itemStyle: {
borderColor: 'transparent',
color: 'transparent'
}
},
data: this.dataSum
},
{
name: '增加',
type: 'bar',
stack: '总量',
label: {
show: true,
position: 'top',
formatter: (params: any) => {
const dataIndex = params.dataIndex;
if (this.dataIn1[dataIndex] !== '-') {
const d1 = this.dataIn1[dataIndex];
const d2 = this.dataIn2[dataIndex] === '-' ? 0 : this.dataIn2[dataIndex];
if (this.currentAccount != 'CD') {
let value = Number(((d1 + d2) * 100).toFixed(2));
value = value < 0 ? value * -1 : value;
return `${value}%`;
} else {
let value = Number((d1 + d2).toFixed(2));
value = value < 0 ? value * -1 : value;
return `${value}`;
}
} else {
return '';
}
}
},
data: this.dataIn1,
itemStyle: {
color: (params: any) => {
const isSpecialPC = [ 'A', 'B','C' ].includes(this.currentAccount);
const fillColor = isSpecialPC ? 'green' : 'red';
return params.name == 'Locked' || params.name == 'Actual' ? '#002b49' : fillColor; //首位两个柱子颜色特殊设置
}
}
},
{
name: '增加',
type: 'bar',
stack: '总量',
label: {
show: false,
position: 'top'
},
data: this.dataIn2,
itemStyle: {
color: (params: any) => {
const isSpecialPC = [ 'A', 'B','C' ].includes(this.currentAccount);
const fillColor = isSpecialPC ? 'green' : 'red';
return params.name == 'Locked' || params.name == 'Actual' ? '#002b49' : fillColor;
}
}
},
{
name: '减少',
type: 'bar',
stack: '总量',
label: {
show: true,
position: 'bottom',
formatter: (params: any) => {
const dataIndex = params.dataIndex;
if (this.dataOut1[dataIndex] !== '-') {
const d1 = this.dataOut1[dataIndex];
const d2 = this.dataOut2[dataIndex] === '-' ? 0 : this.dataOut2[dataIndex];
if (this.currentAccount != 'CD') {
const value = ((d1 - d2 > 0 ? (d1 - d2) * -1 : d1 - d2) * 100).toFixed(2);
return `${value}%`;
} else {
const value = (d1 - d2 > 0 ? (d1 - d2) * -1 : d1 - d2).toFixed(2);
return `${value}`;
}
} else {
return '';
}
}
},
data: this.dataOut1,
itemStyle: {
color: (params: any) => {
const isSpecialPC = [ 'A', 'B','C' ].includes(this.currentAccount);
const fillColor = isSpecialPC ? 'red' : 'green';
return params.name == 'Locked' || params.name == 'Actual' ? '#002b49' : fillColor;
}
}
},
{
name: '减少',
type: 'bar',
stack: '总量',
label: {
show: false,
position: 'bottom'
},
data: this.dataOut2,
itemStyle: {
color: (params: any) => {
const isSpecialPC = [ 'A', 'B','C' ].includes(this.currentAccount);
const fillColor = isSpecialPC ? 'red' : 'green';
return params.name == 'Locked' || params.name == 'Actual' ? '#002b49' : fillColor;
}
}
}
],
//自定义悬浮方块,用来展示variance,对应下面柱状图的柱子
graphic: this.pcdata.conVariance.map((o: any, index: number) => {
const isSpecialPC = [ 'A', 'B','C' ].includes(this.currentAccount);
const fillColor =
o.pc == 'Locked' || o.pc == 'Actual'
? 'white'
: isSpecialPC
? o.variance > 0
? 'green'
: 'red'
: o.variance > 0
? 'red'
: 'green';
let formattedValue = ((o.variance == null ? 0 : o.variance) * 100).toFixed(2);
if (this.currentAccount == 'CD') {
formattedValue = (o.variance == null ? 0 : o.variance).toFixed(2);
} else {
formattedValue = `${formattedValue}%`;
}
return {
type: 'group',
left: `${((index + 0.8) / this.pcdata.conVariance.length) * 92}%`,
top: 10,
children: [
{
type: 'rect',
left: 'center',
top: 'middle',
shape: {
width: 30,
height: 20
},
style: {
fill: fillColor, // 根据值设置背景颜色
lineWidth: 1
}
},
{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: formattedValue,
fill: 'white',
font: '8px sans-serif',
textAlign: 'center',
textVerticalAlign: 'middle'
}
}
]
};
})
};
}
效果展示
由于图片上传不成功,暂时无法添加,后续再补充。
引用
1.Echarts 阶梯瀑布图实现从正数跨到负数,以及负数跨到正数