基础从0实现echarts瀑布图

简介:实现思路纯为本人所想,欢迎交流,考虑拓展场景才自己去实现一个图表,但是单纯也是为了好玩才去实现的,代码冗余部分欢迎给出优化建议,但是均不采纳,因为懒得去优化。

实现思路:图文结合

image.png

image.png

image.png

image.png

/**
 * 将 dataItems 处理成需要的数据结构类型
 * 
 * @param {Object} series - 数据系列,包含需要处理的数据
 * @param {string} position - 数据位置,决定数据的排列顺序
 * @param {string} increase_color - 上升数据的颜色
 * @param {string} decrease_color - 下降数据的颜色
 * @param {string} sum_color - 累计数据的颜色
 * @returns {Array} - 返回经过处理后的数据结构
 */
function processDataItems(
  series,
  position,
  increase_color,
  decrease_color,
  sum_color
) {
  // 获取唯一的 ID 生成器
  const idUniqueHandler = uniqueHandler();
  
  // 复制第一个系列配置
  const seriesItemConfig = { ...series[0] };

  // 创建虚拟对象,用于瀑布图的初始结构
  const virtualObject = {
    ...seriesItemConfig,
    data: [],
    label: { show: false },
    emphasis: { label: { show: false } },
    id: idUniqueHandler('virtualObject#waterFallFigure'),
    relatedSeriesName: seriesItemConfig.name,
    barMinHeight: 0,
    stack: 'Total',
    isVisualSeries: true,
    tooltip: { show: false },
  };

  // 顶部对象,用于显示正数
  const topObject = {
    ...seriesItemConfig,
    data: [],
    stack: 'Total',
  };

  // 底部对象,用于显示负数
  const bottomObject = {
    ...seriesItemConfig,
    data: [],
    id: idUniqueHandler('bottomObject#waterFallFigure'),
    stack: 'Total',
    barMinHeight: 0,
    relatedSeriesName: seriesItemConfig.name,
    isVisualSeries: true,
  };

  const seriesData = series[0].data;
  const len = seriesData.length;
  const resData = [];
  let currentVirtualValue = 0;
  let lastVirtualValue = 0;
  let sum = 0;

  // 遍历数据系列,构建瀑布图数据结构
  for (let i = 0; i < len; i++) {
    const item = seriesData[i];
    sum += item.value;

    const isPositiveValue = Math.sign(item.value) >= 0;
    const isPositiveVirtualValue = Math.sign(currentVirtualValue) >= 0;

    // 处理正数的逻辑
    if (isPositiveValue) {
      if (i === 0 || isPositiveVirtualValue) {
        topObject.data.push({
          ...item,
          value: item.value,
          itemStyle: { color: increase_color },
        });
        bottomObject.data.push({ ...item, value: 0, label: { show: false } });
        virtualObject.data.push({
          ...item,
          value: currentVirtualValue,
          itemStyle: { color: 'transparent' },
          tooltip: { show: false }
        });
        currentVirtualValue += item.value;
      } else {
        lastVirtualValue = currentVirtualValue;
        currentVirtualValue += item.value;
        if (Math.sign(lastVirtualValue) < 0 && Math.sign(currentVirtualValue) < 0) {
          topObject.data.push({
            ...item,
            value: -item.value,
            itemStyle: { color: increase_color },
          });
          bottomObject.data.push({ ...item, value: 0, label: { show: false } });
          virtualObject.data.push({
            ...item,
            value: currentVirtualValue,
            itemStyle: { color: 'transparent' },
            tooltip: { show: false }
          });
        } else {
          topObject.data.push({
            ...item,
            value: -item.value + currentVirtualValue,
            itemStyle: { color: increase_color },
          });
          bottomObject.data.push({ ...item, value: 0, label: { show: false } });
          virtualObject.data.push({
            ...item,
            value: currentVirtualValue,
            itemStyle: { color: increase_color },
          });
          virtualObject.tooltip.show = true;
        }
      }
    } else {
      // 处理负数的逻辑
      if (i === 0 || !isPositiveVirtualValue) {
        topObject.data.push({ ...item, value: 0, label: { show: false } });
        bottomObject.data.push({
          ...item,
          value: item.value,
          itemStyle: { color: decrease_color },
        });
        virtualObject.data.push({
          ...item,
          value: currentVirtualValue,
          itemStyle: { color: 'transparent' },
          tooltip: { show: false }
        });
        currentVirtualValue += item.value;
      } else {
        lastVirtualValue = currentVirtualValue;
        currentVirtualValue += item.value;
        if (Math.sign(lastVirtualValue) > 0 && Math.sign(currentVirtualValue) > 0) {
          topObject.data.push({ ...item, value: 0, label: { show: false } });
          bottomObject.data.push({
            ...item,
            value: -item.value,
            itemStyle: { color: decrease_color },
          });
          virtualObject.data.push({
            ...item,
            value: currentVirtualValue,
            itemStyle: { color: 'transparent' },
            tooltip: { show: false }
          });
        } else {
          topObject.data.push({ ...item, value: 0, label: { show: false } });
          bottomObject.data.push({
            ...item,
            value: -item.value + currentVirtualValue,
            itemStyle: { color: decrease_color },
          });
          virtualObject.data.push({
            ...item,
            value: currentVirtualValue,
            itemStyle: { color: decrease_color },
          });
          virtualObject.tooltip.show = true;
        }
      }
    }
  }

  // 添加累计数据项
  virtualObject.data.push({ value: 0, name: '累计', label: { show: false } });
  topObject.data.push({ value: sum, name: '累计', itemStyle: { color: sum_color } });
  bottomObject.data.push({ value: 0, name: '累计', label: { show: false } });

  // 如果位置在左侧,反转数据顺序
  if (position === 'left') {
    virtualObject.data.reverse();
    topObject.data.reverse();
    bottomObject.data.reverse();
  }

  // 返回处理好的数据结构
  resData.push({ ...virtualObject, barMinHeight: 0 }, { ...topObject, barMinHeight: 0 }, { ...bottomObject, barMinHeight: 0 });
  return resData;
}


/**
 * 处理 formater 数据
 * 
 * @param {Array} resData - 包含瀑布图数据结构的数组
 * @returns {Array} - 返回处理后的数据结构
 */
function processFormaterData(resData) {
  // 获取各个数据对象的引用
  const virtualObjData = resData[0].data;
  const topObjData = resData[1].data;
  const bottomObjData = resData[2].data;

  // 获取虚拟对象数据的长度
  const virtualObjDataLen = virtualObjData.length;

  // 遍历每个虚拟对象数据项,进行格式化处理
  for (let i = 0; i < virtualObjDataLen; i++) {
    // 检查当前虚拟数据项的值是否有效且不透明
    if (virtualObjData[i].value !== 0 && virtualObjData[i].itemStyle.color !== "transparent") {
      // 处理顶部对象数据
      if (topObjData[i].value !== 0 && !isNaN(topObjData[i].value) && !isNaN(virtualObjData[i].value)) {
        _.merge(virtualObjData[i], {
          formatValue: Number(virtualObjData[i].value) + Number(topObjData[i].value)
        });
        _.merge(topObjData[i], {
          formatValue: Number(virtualObjData[i].value) + Number(topObjData[i].value)
        });
      } else {
        // 处理底部对象数据
        if (bottomObjData[i].value !== 0 && !isNaN(virtualObjData[i].value) && !isNaN(bottomObjData[i].value)) {
          _.merge(virtualObjData[i], {
            formatValue: Number(virtualObjData[i].value) + Number(bottomObjData[i].value)
          });
          _.merge(bottomObjData[i], {
            formatValue: Number(virtualObjData[i].value) + Number(bottomObjData[i].value)
          });
        }
      }
    }
  }

  // 合并处理后的数据返回
  _.merge(resData[0], { data: virtualObjData });
  _.merge(resData[1], { data: topObjData });
  _.merge(resData[2], { data: bottomObjData });

  return resData;
}

本文函数中返回的内容为series值,如有疑问欢迎打扰

image.png

Echarts瀑布图是一种特殊的柱状图,用于展示数据的分解和变化情况。瀑布图通过按照维度/指标分解数据,如公司收入按用途分解、公司年利润按分公司分解、业绩按销售团队分解等,来展示数据的变化过程。相对于饼图,瀑布图的优势在于可以清晰辨识拆解项,即使拆解项较多时也不会造成混淆。\[1\] 要绘制Echarts瀑布图,可以使用pyecharts库。首先,需要安装pyecharts库,可以使用pip install pyecharts命令进行安装。然后,可以使用虚拟数据来绘制瀑布图。例如,假设你是一个老板,想要展示2023年公司的总人数、招聘人数和离职人数的变化情况。可以使用Bar类来创建瀑布图,并设置相应的数据和样式。\[2\]\[3\] 以下是一个使用pyecharts库绘制瀑布图的示例代码: ```python from pyecharts.charts import Bar from pyecharts import options as opts # 数据 month = \['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'\] fixed = \[48, 42, 42, 48, 58, 61, 61, 58, 58, 63, 67, 66\] add = \['-', '-', 6, 10, 5, '-', 4, '-', 5, 6, '-', '-'\] reduce = \[2, 6, '-', '-', '-', 2, '-', 7, '-', '-', 2, 1\] # 创建瀑布图对象 bar = Bar() # 添加数据 bar.add_xaxis(month) bar.add_yaxis('各月稳定人数', y_axis=fixed, category_gap=2, stack="人数", itemstyle_opts=opts.ItemStyleOpts(color="rgb(240,240,240)")) bar.add_yaxis('各月增加人数', y_axis=add, stack='人数', category_gap=2) bar.add_yaxis('各月减少人数', y_axis=reduce, stack='人数', category_gap=2) # 设置全局配置 bar.set_global_opts( xaxis_opts=opts.AxisOpts( axislabel_opts=opts.LabelOpts(font_size=8, color='green', font_weight='bolder') ), yaxis_opts=opts.AxisOpts( axislabel_opts=opts.LabelOpts(font_size=10, color='blue', font_weight='bolder') ) ) # 渲染图表 bar.render('people_total_waterfall.html') ``` 以上代码中,我们使用Bar类创建了一个瀑布图对象,并通过add_xaxis和add_yaxis方法添加了数据。然后,使用set_global_opts方法设置了x轴和y轴的样式。最后,使用render方法将图表渲染为HTML文件。 请注意,以上示例中的数据和样式仅供参考,你可以根据自己的需求进行修改和调整。 #### 引用[.reference_title] - *1* [用echarts绘制瀑布图](https://blog.csdn.net/dongdouzin/article/details/117949890)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [循序渐进,学会用pyecharts绘制瀑布图](https://blog.csdn.net/weixin_43790276/article/details/130252095)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值