echarts - 自定义形状 - 实现立体柱状图

如果需要复杂的图表样式,echarts 自带的 series 类型无法满足,那么就可以使用 custom 类型,这时就需要通过 canvas 手动绘制图表的形状了。

知识准备

坐标

假如 echarts 的容器拥有 800*800 尺寸的大小。

在这里插入图片描述

那么,左上角是 (0,0 ),右上角是 (800, 0),左下角是 (0, 800),右下角是 (800, 800)

在这里插入图片描述

value() 函数

仅考虑简单图表。

value() 函数官方说法是根据维度的名称或索引获取对应的数据值。

对于本示例来讲,就是用它获取 x 轴的数据值以及 y 轴的数据值。

value(0) 用于获取 x 轴数据。

value(1) 用于获取 y 轴数据。

获取 X 轴数据值

在本示例中,x 轴类型是 category,所以 value(0) 返回柱子的索引值。

在这里插入图片描述

上面一共有三个个柱子,每个柱子都有自己的索引值,从左至右,从 0 开始。

获取 Y 轴数据值

获取 y 轴数据要简单许多,value(1) 会返回每个柱子的数据值,如:100, 50, 20

coord() 函数

仅考虑简单图表。

类型:coord([x, y])

xy 分别是 x 轴的数据值以及 y 轴的数据值。可以通过 value() 来获取。

coord() 函数的作用是根据 xy 轴的数据值计算出坐标点。

renderItem

series 的类型为 custom 时,通过 renderItem 函数来绘制每个柱子的样子。伪代码:

const options = {
  series: [
    {
      type: "custom",
      data: [100, 50, 20],
      renderItem(params, api) {
        // ...
      }
    }
  ]
}

paramsapi 参考官网:Echarts renderItem

value()coord() 函数就在 api 对象下:api.value()api.coord()

在这个 series 下,data3 个数据,那么 renderItem 就会执行 3 次。

  1. 第一次的数据值是 100
  2. 第二次的数据值是 50
  3. 第三次的数据值是 20

对于 data 中的每个数据项,都会调用一次 renderItem

函数格式如下:

function renderItem(params, api) {
  // ...
  
  return {
    // 我只用过 group
    type: "group",
    // 形状列表
    children: [
      {
        // 已注册的形状名称
        type: "shapeName",
        // 形状参数,会被传递给 extendShape 函数
        shape: {},
        // 形状的样式,可以通过 api.style() 函数来获取基础样式
        style: api.style(),
        // 也可以覆盖某个样式属性,例如单独为每个形状设置不通的背景
        style: {
          ...api.style(),
          fill: "red", // 覆盖基础样式中的 fill
        }
      }
    ]
  }
}

扩展形状

在使用 renderItem 前,必须先使用 echarts.graphic.extendShape 函数来定义形状的样子。

const shapeName = echarts.graphic.extendShape({
  buildPath(ctx, shape) {
    // ...
  }
})

ctx 可以获取到 canvas 的上下文,使用 canvas 来绘制形状。

shape 是使用时传递的 shape 对象数据。参考[renderItem](# 自定义类型)的返回值。

注册形状

要想在 renderItem 中使用形状,需要先扩展形状,最后注册形状。

echarts.graphic.registerShape('shapeName', shapeName);

第一个参数是形状的名称,在使用时直接指定的这个名称即可。

第二个参数是扩展形状的变量名称,例如[扩展形状](# 扩展形状)中的变量名 shapeName

基础柱状图

我们会将下面的柱状图改造成立体柱状图。

在这里插入图片描述

const options = {
  title: {
    text: "不同级别的忍者数量",
    textStyle: {
      color: "#fff",
    },
  },

  backgroundColor: "#000",

  xAxis: {
    type: "category",
    data: ["下忍", "中忍", "上忍"],
    axisLabel: {
      color: "#fff",
    },
  },

  yAxis: {
    type: "value",
    max: 200,
    axisLabel: {
      color: "#fff",
    },
    splitLine: {
      lineStyle: {
        color: "#222",
      },
    },
  },

  tooltip: {
    trigger: "axis",
  },

  series: [
    {
      type: "bar",
      data: [100, 50, 20],
      barWidth: 30,
    },
  ],
};

立体柱状图解析

在这里插入图片描述

在立体柱状图中,一共有三面:左侧面、右侧面、顶面

每一面都对应一个形状,一共需要创建三个形状。

顶部基础 y 轴是顶面每个点坐标的基础计算值。

基础 x 轴是左侧面和右侧面每个点坐标的基础计算值。

斜角高度:从中心点到两个侧面顶角的高度。

中心点:是 coord([value(index, value)]) 计算后的坐标点。

分解

在这里插入图片描述

这些点就是我们要在 renderItem 中绘制的。

代码实现

修改 series,并创建 renderItem 函数:

function renderItem(params, api) {}

const options = {
  series: [
    {
      type: "custom",
      data: [100, 50, 20],
      renderItem,
    },
  ],
}

然后先创建三个形状的空壳子,并注册:

const leftShape = echarts.graphic.extendShape({
  buildPath(ctx, shape) {},
});

const rightShape = echarts.graphic.extendShape({
  buildPath(ctx, shape) {},
});

const topShape = echarts.graphic.extendShape({
  buildPath(ctx, shape) {},
});

echarts.graphic.registerShape("leftShape", leftShape);
echarts.graphic.registerShape("rightShape", rightShape);
echarts.graphic.registerShape("topShape", topShape);

完善 renderItem 函数,获取基础坐标并将坐标传递给自定义形状中:

function renderItem(params, api) {
  // 基础坐标
  const basicsCoord = api.coord([api.value(0), api.value(1)]);
  // 顶部基础 y 轴
  const topBasicsYAxis = basicsCoord[1];
  // 基础 x 轴
  const basicsXAxis = basicsCoord[0];
  // 底部 y 轴
  const bottomYAxis = api.coord([api.value(0), 0])[1];

  return {
    type: "group",
    children: [
      {
        type: "leftShape",
        shape: {
          topBasicsYAxis,
          basicsXAxis,
          bottomYAxis,
        },
        style: api.style(),
      },
      {
        type: "rightShape",
        shape: {
          topBasicsYAxis,
          basicsXAxis,
          bottomYAxis,
        },
        style: api.style(),
      },
      {
        type: "topShape",
        shape: {
          topBasicsYAxis,
          basicsXAxis,
          bottomYAxis,
        },
        style: api.style(),
      },
    ],
  };
}

完善 leftShape 形状逻辑:

const leftShape = echarts.graphic.extendShape({
  buildPath(ctx, shape) {
    const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
    // 侧面宽度
    const WIDTH = 15;
    // 斜角高度
    const OBLIQUE_ANGLE_HEIGHT = 3.6;

    const p1 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
    const p2 = [basicsXAxis - WIDTH, bottomYAxis];
    const p3 = [basicsXAxis, bottomYAxis];
    const p4 = [basicsXAxis, topBasicsYAxis];

    ctx.moveTo(p1[0], p1[1]);
    ctx.lineTo(p2[0], p2[1]);
    ctx.lineTo(p3[0], p3[1]);
    ctx.lineTo(p4[0], p4[1]);
  },
});

完善 rightShape 形状逻辑:

const rightShape = echarts.graphic.extendShape({
  buildPath(ctx, shape) {
    const { topBasicsYAxis, bottomYAxis, basicsXAxis } = shape;
    // 侧面宽度
    const WIDTH = 15;
    // 斜角高度
    const OBLIQUE_ANGLE_HEIGHT = 3.6;

    const p1 = [basicsXAxis, topBasicsYAxis];
    const p2 = [basicsXAxis, bottomYAxis];
    const p3 = [basicsXAxis + WIDTH, bottomYAxis];
    const p4 = [basicsXAxis + WIDTH, topBasicsYAxis + OBLIQUE_ANGLE_HEIGHT];

    ctx.moveTo(p1[0], p1[1]);
    ctx.lineTo(p2[0], p2[1]);
    ctx.lineTo(p3[0], p3[1]);
    ctx.lineTo(p4[0], p4[1]);
  },
});

完善 topShape 形状逻辑:

const topShape = echarts.graphic.extendShape({
  buildPath(ctx, shape) {
    const { topBasicsYAxis, basicsXAxis } = shape;
    // 侧面宽度
    const WIDTH = 15;
    // 斜角高度
    const OBLIQUE_ANGLE_HEIGHT = 3.6;

    const p1 = [basicsXAxis, topBasicsYAxis];
    const p2 = [basicsXAxis + WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];
    const p3 = [basicsXAxis, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT * 2];
    const p4 = [basicsXAxis - WIDTH, topBasicsYAxis - OBLIQUE_ANGLE_HEIGHT];

    ctx.moveTo(p1[0], p1[1]);
    ctx.lineTo(p2[0], p2[1]);
    ctx.lineTo(p3[0], p3[1]);
    ctx.lineTo(p4[0], p4[1]);
  },
});

最后为每个面都设置不同的颜色装饰一下:

function renderItem(params, api) {
  // 柱子索引值
  const { seriesIndex } = params;

  // 基础坐标
  const basicsCoord = api.coord([api.value(seriesIndex), api.value(1)]);
  // 顶部基础 y 轴
  const topBasicsYAxis = basicsCoord[1];
  // 基础 x 轴
  const basicsXAxis = basicsCoord[0];
  // 底部 y 轴
  const bottomYAxis = api.coord([api.value(seriesIndex), 0])[1];

  return {
    type: "group",
    children: [
      {
        type: "leftShape",
        shape: {
          topBasicsYAxis,
          basicsXAxis,
          bottomYAxis,
        },
        style: {
          ...api.style(),
          fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "rgba(34, 7, 94, 1)" },
            { offset: 1, color: "rgba(0, 0, 0, 0.26)" },
          ]), // 覆盖基础样式
        },
      },
      {
        type: "rightShape",
        shape: {
          topBasicsYAxis,
          basicsXAxis,
          bottomYAxis,
        },
        style: {
          ...api.style(),
          fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: "rgba(57, 16, 133, 1)" },
            { offset: 1, color: "rgba(0, 0, 0, 0.26)" },
          ]), // 覆盖基础样式
        },
      },
      {
        type: "topShape",
        shape: {
          topBasicsYAxis,
          basicsXAxis,
          bottomYAxis,
        },
        style: {
          ...api.style(),
          fill: "#722ed1", // 覆盖基础样式
        },
      },
    ],
  };
}

效果图

在这里插入图片描述

参考

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值