echarts 自定义形状 画圆柱体

132 阅读2分钟

效果图

image.png

将下面代码复制到 echarts.apache.org/examples/zh… 可直接浏览

const data = [90, 40, 20] //数据
const total = data.reduce((a,b)=> a+b) //总数,用来计算占比和显示背景圆柱

//画圆柱的椭圆顶部
function pathEllipse(ctx, centerX, centerY, a, b) {
  var k = 0.5522848,
    ox = a * k, // 水平控制点偏移量
    oy = b * k; // 垂直控制点偏移量
  //从椭圆的左端点开始顺时针绘制四条三次贝塞尔曲线
  ctx.moveTo(centerX - a, centerY);
  ctx.bezierCurveTo(
    centerX - a,
    centerY - oy,
    centerX - ox,
    centerY - b,
    centerX,
    centerY - b
  );
  ctx.bezierCurveTo(
    centerX + ox,
    centerY - b,
    centerX + a,
    centerY - oy,
    centerX + a,
    centerY
  );
  ctx.bezierCurveTo(
    centerX + a,
    centerY + oy,
    centerX + ox,
    centerY + b,
    centerX,
    centerY + b
  );
  ctx.bezierCurveTo(
    centerX - ox,
    centerY + b,
    centerX - a,
    centerY + oy,
    centerX - a,
    centerY
  );
  ctx.closePath();
  ctx.stroke();
}
//画圆柱的侧面
function pathBottom(ctx, centerX, centerY, a, b, bottomYAxis) {
  var k = 0.5522848,
    ox = a * k, // 水平控制点偏移量
    oy = b * k; // 垂直控制点偏移量
  const [x, y] = [centerX, centerY];
  ctx.moveTo(x + a, y); //椭圆右端点开始顺时针绘制两条曲线
  ctx.bezierCurveTo(x + a, y + oy, x + ox, y + b, x, y + b);
  ctx.bezierCurveTo(x - ox, y + b, x - a, y + oy, x - a, y);
  ctx.lineTo(x - a, bottomYAxis); //从上到下的直线
  const [bx, by] = [centerX, bottomYAxis]; //底部椭圆左侧端点开始逆时针绘制两条曲线
  ctx.bezierCurveTo(bx - a, by + oy, bx - ox, by + b, bx, by + b);
  ctx.bezierCurveTo(bx + ox, by + b, bx + a, by + oy, bx + a, by);
  ctx.lineTo(bx + a, centerY); //从下到上的直线
  ctx.closePath();
  ctx.stroke();
}


// 注册椭圆
echarts.graphic.registerShape('shapeEllipse', echarts.graphic.extendShape({
  shape: {},
  buildPath(ctx, shape) {
    const { EllipseBaseData, EllipsePosition } = shape;
    pathEllipse(
      ctx,
      EllipsePosition.centerX,
      EllipsePosition.centerY,
      EllipseBaseData.a,
      EllipseBaseData.b
    );
  }
}));
// 注册圆柱侧面
echarts.graphic.registerShape('shapeCylinderSide', echarts.graphic.extendShape({
  shape: {},
  buildPath(ctx, shape) {
    const { EllipseBaseData, EllipsePosition } = shape;
    pathBottom(
      ctx,
      EllipsePosition.centerX,
      EllipsePosition.centerY,
      EllipseBaseData.a,
      EllipseBaseData.b,
      EllipsePosition.bottomYAxis
    );
  }
}));


option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed']
  },
  yAxis: {
    type: 'value',
    max: total
  },
  tooltip: {
    show: true,
    trigger: 'item',
    backgroundColor: '#ffffff',
    padding: [8, 16, 12, 16],
    borderWidth: 0,
    extraCssText:
      'box-shadow: 0px 4px 10px rgba(123, 135, 178, 0.2);border-radius: 6px;',
    formatter: (params) => {
      return `<div>
      <span style="width:8px;height:8px;display:inline-block;border-radius:50%;margin-right: 6px; vertical-align:baseline; background: ${
        ['#0071FA', '#2eb3fd', '#17ceda'][params.dataIndex] || '#0071FA'
      };"></span>
      <span>${params.name}学习知识点</span>
      <span style="font-size:14px;line-height: 20px;
      font-weight: 700;color:#2B2E35;padding-left: 16px;">${params.value}</span>
      </div>`;
    },
    textStyle: {
      color: '#656A72',
      fontSize: 12,
      lineHeight: 18
    }
  },
  grid: {
    width: 300,
    height: 200
  },
  series: [
    {
      data: data,
      type: 'custom',
      itemStyle: {
        color: (params) => {
          return (
            [
              'rgba(0, 113, 250, 1)',
              'rgba(46, 179, 253, 1)',
              'rgba(22, 206, 218, 1)'
            ][params.dataIndex] || 'rgba(0, 113, 250, 1)'
          );
        }
      },
      renderItem: (params, api) => {
        // 基础坐标
        const basicsCoord = api.coord([api.value(0), api.value(1)]);
        // 圆柱底部 y 轴
        const bottomYAxis = api.coord([api.value(0), 0])[1];
        // 背景圆柱顶部 y 轴
        const bgTopYAxis = api.coord([api.value(0), total])[1];

        const EllipseBaseData = {
          //椭圆的数据
          a: 19, //半长轴
          b: 5 //半短轴
        };
        return {
          type: 'group',
          children: [
            {
              type: 'shapeEllipse', //背景顶部的椭圆
              shape: {
                EllipseBaseData,
                EllipsePosition: {
                  centerX: basicsCoord[0],
                  centerY: bgTopYAxis
                }
              },
              style: {
                fill: api.style().fill.replace(', 1)', ', 0.4)')
              }
            },
            {
              type: 'shapeCylinderSide', //背景椭圆的侧面
              shape: {
                EllipseBaseData,
                EllipsePosition: {
                  centerX: basicsCoord[0],
                  centerY: bgTopYAxis,
                  bottomYAxis
                }
              },
              style: {
                fill: api.style().fill.replace(', 1)', ', 0.2)')
              }
            },
            {
              type: 'shapeEllipse', //实际顶部的椭圆
              shape: {
                EllipseBaseData,
                EllipsePosition: {
                  centerX: basicsCoord[0],
                  centerY: basicsCoord[1]
                }
              },
              style: {
                fill: api.style().fill
              }
            },
            {
              type: 'shapeCylinderSide', //实际椭圆的侧面
              shape: {
                EllipseBaseData,
                EllipsePosition: {
                  centerX: basicsCoord[0],
                  centerY: basicsCoord[1],
                  bottomYAxis
                }
              },
              style: {
                fill: api.style().fill.replace(', 1)', ', 0.6)')
              }
            },
            {
              type: 'text', //圆柱顶部数据 xx%
              style: {
                text: Math.round(api.value(1)*100/total) + '%',
                fontSize: 10,
                x: basicsCoord[0],
                y: bgTopYAxis - 15,
                align: 'center',
                verticalAlign: 'middle',
                fill: 'rgba(171, 176, 191, 1)'
              }
            }
          ]
        };
      }
    }
  ]
};