Echart瀑布图

Echarts+Angular 阶梯瀑布图实现跨象限算法

用于财务系统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 阶梯瀑布图实现从正数跨到负数,以及负数跨到正数

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值