乐趣区

关于前端:精装echarts漏斗图两侧label

背景

echarts 官网的漏斗图和 excel 里漏斗图不太一样,记录本人尝试配置漏斗图的过程 (次要是两侧 label 的实现)


echarts 官网漏斗图示例

尝试一

思考只用一个漏斗图示例,默认是 label 的 position 为 left,右侧 label 基于 markLine 实现

const chartFactWidth = 400; // 漏斗图的宽度
const chartMarginLeft = 200; // 漏斗图间隔左侧间隔
const chartCenterX = chartFactWidth / 2 + chartMarginLeft;
const minSize = 40;
const maxSize = chartFactWidth;
const percentSize = (maxSize - minSize) / 100.0;
const chartFactValueWidth = maxSize - minSize;

option = {
  title: {text: 'Funnel'},
  tooltip: {
    trigger: 'item',
    formatter: '{a} <br/>{b} : {c}%'
  },
  toolbox: {
    feature: {dataView: { readOnly: false},
      restore: {},
      saveAsImage: {}}
  },
  series: [
    {
      name: 'Funnel',
      type: 'funnel',
      left: chartMarginLeft,
      top: '10%',
      bottom: '10%',
      width: chartFactWidth,
      min: 10,
      max: 100,
      minSize: minSize,
      maxSize: maxSize,
      sort: 'descending',
      gap: 0,
      label: {
        show: true,
        position: 'left',
        formatter: function (params) {
          return ('{a| 转化率}' + '\n' + '{b|' + params.data.percent * 100 + '%}'
          );
        },
        rich: {
          a: {
            color: 'darkgray',
            lineHeight: 30
          },
          b: {height: 20}
        }
      },
      labelLayout(params) {console.log(params.rect.x + params.rect.width, params.rect.width);
        return {
          x: 50, //params.rect.x ,//50,
          y: params.rect.y + params.rect.height / 2,
          verticalAlign: 'middle',
          align: 'left'
        };
      },
      labelLine: {
        lineStyle: {
          width: 1,
          type: 'solid',
          color: '#ccc'
        }
      },
      itemStyle: {borderWidth: 0},
      emphasis: {
        label: {fontSize: 20}
      },
      data: [
        {
          value: 100,
          name: '量 1',
          percent: Math.round((90 / 100) * 100, 2) / 100
        },
        {
          value: 90,
          name: '量 2',
          percent: Math.round((60 / 90) * 100, 2) / 100
        },
        {
          value: 60,
          name: '量 3',
          percent: Math.round((50 / 60) * 100, 2) / 100
        },
        {
          value: 50,
          name: '量 4',
          percent: Math.round((20 / 50) * 100, 2) / 100
        },
        {
          value: 20,
          name: '量 5',
          percent: Math.round((10 / 50) * 100, 2) / 100
        }
      ],
      markLine: {
        lineStyle: {
          width: 1,
          type: 'solid',
          color: '#ccc'
        },
        symbol: [],
        data: [
          [
            {
              name: '量 1\n 值 100',
              x: chartCenterX + minSize / 2 + (maxSize - minSize) / 2,
              y: '10%'
            },
            {
              x: '90%',
              y: '10%'
            }
          ],
          [
            {
              name: '量 2\n 值 90',
              x: chartCenterX + minSize / 2 + ((maxSize - minSize) / 2) * 0.9,
              y: '26%'
            },
            {
              x: '90%',
              y: '26%'
            }
          ],
          [
            {
              name: '量 3\n 值 60',
              x:
                chartCenterX +
                minSize / 2 +
                ((maxSize - minSize) / 2) * 0.9 * 0.67,
              y: '42%'
            },
            {
              x: '90%',
              y: '42%'
            }
          ],
          [
            {
              name: '量 4\n 值 50',
              x:
                chartCenterX +
                minSize / 2 +
                ((maxSize - minSize) / 2) * 0.9 * 0.67 * 0.83,
              y: '58%'
            },
            {
              x: '90%',
              y: '58%'
            }
          ],
          [
            {
              name: '量 5\n 值 20',
              x:
                chartCenterX +
                minSize / 2 +
                ((maxSize - minSize) / 2) * 0.9 * 0.67 * 0.83 * 0.4,
              y: '74%'
            },
            {
              x: '90%',
              y: '74%'
            }
          ],
          [
            {
              name: '量 6\n 值 0',
              x: chartCenterX + minSize / 2,
              y: '90%'
            },
            {
              x: '90%',
              y: '90%'
            }
          ]
        ]
      }
    }
  ]
};

问题

  • Q1:markLine 的起始点要本人计算,目前上述示例起始点计算不精确的;(这个回头空了再看了,各位同学亲们如果感兴趣能够看下)
  • Q2:(思路)如果所需的漏斗图没有 gap(图形间距),且右侧的线能够和自身图形色彩一样,那能够间接把中心点作为起始点即可,这个计划也能够用(绝对计划四,少绘制一个漏斗图)。

尝试二

基于上述尝试一,因为每个右上角的点要自行计算,所以左右线实现形式调换。思考只用一个漏斗图示例,默认(右侧)是 label 的 position 为 rightTop,左侧 label 基于 markLine 实现,而后还要自行补充一条右侧 markLine。

const topPercent = 5;
const heightPercent = 90;
const data = [{ value: 80, name: 'Visit'},
  {value: 40, name: 'Cart'},
  {value: 20, name: 'Order'},
  {value: 100, name: 'Show'}
];
const itemCount = data.length;
const itemHeightPercent = heightPercent / itemCount;

option = {
  tooltip: {
    trigger: 'item',
    formatter: '{a} <br/>{b} : {c}%'
  },
  series: [
    {
      name: 'Funnel',
      type: 'funnel',
      left: '20%',
      top: topPercent + '%',
      bottom: 100 - topPercent - heightPercent + '%',
      width: '60%',
      min: 0,
      max: 100,
      minSize: '0%',
      maxSize: '100%',
      sort: 'descending',
      gap: 2,
      zLevel: 2,
      label: {
        show: true,
        position: 'rightTop',
        formatter: '{b}\n{c}'
      },
      labelLayout: function (params) {
        return {
          x: '90%',
          y: params.rect.y
        };
      },
      labelLine: {
        length: 10,
        lineStyle: {
          width: 1,
          type: 'solid'
        }
      },
      markLine: {
        symbol: 'none',
        lineStyle: {type: 'solid'},
        label: {formatter: function (params) {return params.name;},
          rich: {
            a: {
              color: 'darkgray',
              lineHeight: 30
            },
            b: {height: 20}
          }
        },
        data: [
          [
            {
              x: '50%',
              y: topPercent + itemHeightPercent * 0.5 + '%',
              name: '{a| 转化率}\n{b|' + (80 * 100.0) / 100 + '%}'
            },
            {
              x: '10%',
              y: topPercent + itemHeightPercent * 0.5 + '%',
              name: '{a| 转化率}\n{b|' + (80 * 100.0) / 100 + '%}'
            }
          ],
          [
            {
              x: '50%',
              y: topPercent + itemHeightPercent * (1 + 0.5) + '%',
              name: '{a| 转化率}\n{b|' + (40 * 100.0) / 80 + '%}'
            },
            {
              x: '10%',
              y: topPercent + itemHeightPercent * (1 + 0.5) + '%'
            }
          ],
          [
            {
              x: '50%',
              y: topPercent + itemHeightPercent * (2 + 0.5) + '%',
              name: '{a| 转化率}\n{b|' + (20 * 100.0) / 40 + '%}'
            },
            {
              x: '10%',
              y: topPercent + itemHeightPercent * (2 + 0.5) + '%'
            }
          ],
          [
            {
              x: '50%',
              y: topPercent + itemHeightPercent * (3 + 0.5) + '%',
              name: '{a| 转化率}\n{b|' + (0 * 100.0) / 20 + '%}'
            },
            {
              x: '10%',
              y: topPercent + itemHeightPercent * (3 + 0.5) + '%'
            }
          ],
          [
            {
              x: '50%',
              y: topPercent + itemHeightPercent * 4 + '%',
              name: 'ReOrder'
            },
            {
              x: '90%',
              y: topPercent + itemHeightPercent * 4 + '%'
            }
          ]
        ]
      },
      itemStyle: {
        borderColor: '#fff',
        borderWidth: 1
      },
      emphasis: {
        label: {fontSize: 20}
      },
      data: data
    }
  ]
};

问题

  • Q1:右侧最初一条是 markLine,其动画和原 series 的 labelLine 的动画是否能对立;
  • Q2:markLine 没有看到设置层级的属性,如果 markLine 能设置在漏斗图的底部,那左侧线完结点设置为中心点的话(如上图)能不便些;
  • Q3:这个计划画线是可行的,思路是先渲染漏斗图和右侧 label,在 labelLayout 里获取理论 item 的 rect 的 x、y 地位,再依据后面获取到的地位数据计算绘制出左侧 markLine。(但这个问题解决了,问题 1 还是得解决才比拟残缺)

尝试三

思考用 2 个漏斗图实例(重叠),右侧 rightTop,最上面那条线用 markLine 绘制

const data = [{ value: 60, name: 'Visit'},
  {value: 40, name: 'Inquiry'},
  {value: 20, name: 'Order'},
  {value: 80, name: 'Click'},
  {value: 100, name: 'Show'}
];
option = {
  title: {text: 'Funnel'},
  tooltip: {
    trigger: 'item',
    formatter: '{a} <br/>{b} : {c}%'
  },
  toolbox: {
    feature: {dataView: { readOnly: false},
      restore: {},
      saveAsImage: {}}
  },
  legend: {data: ['Show', 'Click', 'Visit', 'Inquiry', 'Order']
  },
  series: [
    {
      name: 'Funnel',
      type: 'funnel',
      left: '25%',
      top: '10%',
      bottom: '10%',
      width: '50%',
      min: 0,
      max: 100,
      minSize: '0%',
      maxSize: '100%',
      sort: 'descending',
      gap: 2,
      label: {
        show: true,
        position: 'left'
      },
      labelLayout: function (params) {
        return {x: '15%'};
      },
      labelLine: {
        length: 10,
        lineStyle: {
          width: 1,
          type: 'solid'
        }
      },
      itemStyle: {
        borderColor: '#fff',
        borderWidth: 1
      },
      emphasis: {
        label: {fontSize: 20}
      },
      data: data,
      zlevel: 2
    },
    {
      name: 'Funnel',
      type: 'funnel',
      left: '25%',
      top: '10%',
      bottom: '10%',
      width: '50%',
      min: 0,
      max: 100,
      minSize: '0%',
      maxSize: '100%',
      sort: 'descending',
      gap: 2,
      label: {
        show: true,
        position: 'rightTop',
        animation: false,
        color: '#666'
      },
      labelLayout: function (params) {
        return {x: '85%'};
      },
      labelLine: {
        length: 10,
        lineStyle: {
          width: 1,
          type: 'solid',
          color: '#ccc'
        }
      },
      markLine: {
        symbol: 'none',
        lineStyle: {
          type: 'solid',
          color: '#ccc'
        },
        label: {
          color: '#333',
          animation: false
        },
        data: [
          [
            {
              x: '50%',
              y: '90%',
              name: 'ReOrder'
            },
            {
              x: '85%',
              y: '90%',
              name: 'ReOrder'
            }
          ]
        ]
      },
      itemStyle: {
        borderColor: '#fff',
        borderWidth: 1
      },
      emphasis: {show: false},
      data: data,
      zlevel: 1
    }
  ]
};

问题

  • Q1:与上述”尝试二“一样的问题,markLine 绘制的 label 和 line 的动画成果 和 hover 成果须要调整和 lableline 的一样

尝试四

思考用 2 个漏斗图示例(重叠),一个 label 的 position 为 left,一个为 rightTop。右侧有 label 的漏斗图多一层级,满足右侧 line 的性能,再暗藏多的那层级的 item。

const data1 = [{ value: 60, name: 'Visit'},
  {value: 40, name: 'Inquiry'},
  {value: 20, name: 'Order'},
  {value: 80, name: 'Click'},
  {value: 100, name: 'Show'}
];
const data2 = [{ value: 60, name: 'Visit'},
  {value: 40, name: 'Inquiry'},
  {value: 20, name: 'Order'},
  {
    value: 0,
    name: 'ReOrder',
    itemStyle: {
      borderColor: '#fff',
      borderWidth: 1,
      opacity: 0
    },
    labelLine: {opacity: 1},
    label: {opacity: 1}
  },
  {value: 80, name: 'Click'},
  {value: 100, name: 'Show'}
];
const chart1_heightPercent = 80; // 漏斗图 2 高度比例
const chart2_heightPercent =
  (chart1_heightPercent / data1.length) * data2.length; // 漏斗图 2 高度比例

option = {
  tooltip: {
    trigger: 'item',
    formatter: '{a} <br/>{b} : {c}%'
  },
  series: [
    {
      name: 'Funnel',
      type: 'funnel',
      left: '20%',
      top: 60,
      bottom: 60,
      width: '60%',
      height: chart1_heightPercent + '%',
      min: 0,
      max: 100,
      minSize: '20%',
      maxSize: '100%',
      sort: 'descending',
      gap: 2,
      label: {
        show: true,
        position: 'left',
        formatter: '{b}\n{c}%'
      },
      labelLayout: function (params) {
        return {x: '10%'};
      },
      labelLine: {
        length: 60,
        lineStyle: {
          width: 1,
          type: 'solid'
        }
      },
      itemStyle: {
        borderColor: '#fff',
        borderWidth: 1,
        normal: {opacity: 0.8}
      },
      emphasis: {
        label: {fontSize: 20}
      },
      data: data1,
      zlevel: 2
    },
    {
      name: 'Funnel',
      type: 'funnel',
      left: '20%',
      top: 60,
      bottom: 60,
      width: '60%',
      height: chart2_heightPercent + '%',
      min: 0,
      max: 100,
      minSize: '20%',
      maxSize: '100%',
      sort: 'descending',
      gap: 2,
      label: {
        show: true,
        position: 'rightTop'
      },
      labelLine: {
        length: 80,
        lineStyle: {
          width: 1,
          type: 'solid'
        }
      },
      itemStyle: {
        borderColor: '#fff',
        borderWidth: 1,
        normal: {opacity: 0.5}
      },
      emphasis: {
        label: {fontSize: 20}
      },
      data: data2,
      zlevel: 1
    }
  ]
};

这个计划绝对比拟“完满”,外围是虚造多一个层级的数据,让 n 层级漏斗 和 n+ 1 层级漏斗 完满重叠,从而满足 item 有 labelline,item 分界线也有 lableline。

总结

echarts 图表配置项比拟多,大多都要一个个属性去配置测试。

官网漏斗图样例比较简单,且短时间内没有搜到和 excel 相似的例子,网上也不少人在发问。

本文是我跟着本人的思路去做的尝试,计划四也是基于后面的尝试后,思考能不计算 markLine 地位、不思考 markLine/labelLine 的动画统一这些问题,还是抉择了“重叠”的计划,而后再想着怎么天然的多一条标记线,最初思考调整(不同层级数的)两个漏斗图配置参数使其重叠。

这是我第一篇对外输入的在线开发笔记,很开心的。

解决方案个别都不止一种,文中我也有不少还没解决的问题和思路,也想求教各位同学亲们的,如果感兴趣,或者其余想法,欢送大家留言一起分享或探讨的,谢谢!~

文献参考

  • echarts 漏斗图的标示线和标识文字对齐
  • echarts 官网配置项手册
退出移动版