echarts自定义renderItem柱状图,每个系列柱子数不一样

话不多说,先上图

业务需求是,在【前期库存中】,显示四年的数量;后面的1-12月每个系列中最多显示两个柱子。

在echarts官方实例中,系列的数量是由 series 数组中的数量决定的,也就是说该数组中有几个数据项,就有几根柱子,但是每个系列项的柱子数是一样的。这样是不满足我的需求的。所以接下来就要开始寻找其他解决方式。

后来发现了echarts提供了自定义的渲染方式,series-custom,链接传送

1、开发者自定义渲染逻辑(renderItem 函数)

custom 系列需要开发者自己提供图形渲染的逻辑,这个渲染逻辑一般是通过 renderItem 实现,例如

var option = {
    ...,
    series: [{
        type: 'custom',
        renderItem: function (params, api) {
            // ...
        },
        data: data
    }]
}

data 中的每个数据项,都会调用 renderItem 函数。renderItem 函数的作用:返回一个(或者一组)图形元素定义,图形元素定义了图形元素的类型、位置、尺寸、样式等

ECharts 会根据这些 图形元素定义 来渲染出图形元素。

var option = {
    ...,
    series: [{
        type: 'custom',
        renderItem: function (params, api) {
            // 对于 data 中的每个 dataItem,都会调用这个 renderItem 函数。
            // (但是注意,并不一定是按照 data 的顺序调用)

            // 这里进行一些处理,例如,坐标转换。
            // 这里使用 api.value(0) 取出当前 dataItem 中第一个维度的数值。
            var categoryIndex = api.value(0);
            // 这里使用 api.coord(...) 将数值在当前坐标系中转换成为屏幕上的点的像素值。
            var startPoint = api.coord([api.value(1), categoryIndex]);
            var endPoint = api.coord([api.value(2), categoryIndex]);
            // 这里使用 api.size(...) 获得 Y 轴上数值范围为 1 的一段所对应的像素长度。
            var height = api.size([0, 1])[1] * 0.6;

            // 这里返回为这个 dataItem 构建的图形元素定义。
            return {
                // 表示这个图形元素是矩形。还可以是 'circle', 'sector', 'polygon' 等等。
                type: 'rect',
                // shape 属性描述了这个矩形的像素位置和大小。
                // 其中特殊得用到了 echarts.graphic.clipRectByRect,意思是,
                // 如果矩形超出了当前坐标系的包围盒,则剪裁这个矩形。
                shape: echarts.graphic.clipRectByRect({
                    // 矩形的位置和大小。
                    x: startPoint[0],
                    y: startPoint[1] - height / 2,
                    width: endPoint[0] - startPoint[0],
                    height: height
                }, {
                    // 当前坐标系的包围盒。
                    x: params.coordSys.x,
                    y: params.coordSys.y,
                    width: params.coordSys.width,
                    height: params.coordSys.height
                }),
                // 用 api.style(...) 得到默认的样式设置。这个样式设置包含了
                // option 中 itemStyle 的配置和视觉映射得到的颜色。
                style: api.style()
            };
        },
        data: [
            [12, 44, 55, 60], // 这是第一个 dataItem
            [53, 31, 21, 56], // 这是第二个 dataItem
            [71, 33, 10, 20], // 这是第三个 dataItem
            ...
        ]
    }]
}

这里主要用到了几个方法:

  • api.value(...)   得到给定维度的数据值
    • @param {number} dimension 指定的维度(维度从 0 开始计数)。
      @param {number} [dataIndexInside] 一般不用传,默认就是当前数据项的 dataIndexInside。
      @return {number} 给定维度上的值。

       

  • api.coord(...)  将数据值映射到坐标系上
    • @param {Array.<number>} data 数据值。
      @return {Array.<number>} 画布上的点的坐标,至少包含:[x, y]
              对于polar坐标系,还会包含其他信息:
              polar: [x, y, radius, angle]

       

  • api.size(...) 给定数据范围,映射到坐标系上后的长度。

    例如,cartesian2d中,api.size([2, 4]) 返回 [12.4, 55],表示 x 轴数据范围为 2 映射得到长度是 12.4,y 轴数据范围为 4 时应设得到长度为 55。

    在一些坐标系中,如极坐标系(polar)或者有 log 数轴的坐标系,不同点的长度是不同的,所以需要第二个参数,指定获取长度的点

@param {Array.<number>} dataSize 数据范围。
@param {Array.<number>} dataItem 获取长度的点。
@return {Array.<number>} 画布上的长度

了解以上概念之后,直接上代码。

2、开发过程

由于是在vue中开发的,小伙伴们可以自行调整框架。

<template>
  <div class="fillcontain withdrawNotes">
    <div class="anicommon">
      <div id="demo"></div>
    </div>
  </div>
</template>
<script>
import echarts from 'echarts'
var data = []
data.push({ name: '1', value: [0, 0, 120, 1, 4] })
data.push({ itemStyle: { normal: { color: '#1bbcf3' } }, name: '2', value: [0, 120, 220, 1, 4] })
data.push({ itemStyle: { normal: { color: 'lightgreen' } }, name: '3', value: [0, 0, 150, 2, 4] })

data.push({ itemStyle: { normal: { color: '#61a0a8' } }, name: '4', value: [0, 0, 200, 3, 4] })

data.push({ itemStyle: { normal: { color: '#715881' } }, name: '5', value: [0, 0, 200, 4, 4] })
// data.push({ itemStyle: { normal: { color: '#715881' } }, name: '2011', value: [0, 0, 200, 5, 5] })
data.push({ name: '6', value: [1, 0, 250, 1, 3] })
data.push({ name: '7', value: [1, 0, 280, 2, 3] })
data.push({ itemStyle: { normal: { color: '#91c7ae' } }, name: '8', value: [1, 0, 280, 3, 3] })
// data.push({ itemStyle: { normal: { color: 'lightgreen' } }, name: '2012', value: [0, 150, 350, 200] })
// data.push({ itemStyle: { normal: { color: 'lightgreen' } }, name: '2012', value: [1, 250, 320, 200] })
data.push({ name: '9', value: [2, 0, 330, 1, 1] })
data.push({ itemStyle: { normal: { color: '#2f4554' } }, name: '10', value: [3, 0, 430, 1, 2] })
data.push({ itemStyle: { normal: { color: 'lightgreen' } }, name: '11', value: [3, 0, 230, 2, 2] })
console.log(data)
export default {
  data() {
    return {
      option: {
        tooltip: {
          trigger: 'item'
        },
        xAxis: {
          type: 'category',
          data: ['系列1', '系列2', '系列3', '系列4'],
          splitArea: {},
          splitLine: { show: true }
        },
        yAxis: { type: 'value' },
        series: [
          {
            type: 'custom',
            renderItem: this.renderItem,
            itemStyle: { normal: { opacity: 0.8 } },
            encode: { y: [1, 2], x: 0 },
            data: data
          }
        ]
      }
    }
  },
  methods: {
    lineRender: function() {
      var myChart = echarts.init(document.getElementById('demo'))
      myChart.setOption(this.option)
    },
    renderItem: function(params, api) {
      let categoryIndex = api.value(0)
      let start = api.coord([categoryIndex, api.value(1)])
      let end = api.coord([categoryIndex, api.value(2)])
      let width = api.size([0, api.value(2)])[0] * 0.8

      const num = api.value(4) // 每个系列柱子数
      const currentIndex = api.value(3)
      const isOdd = num % 2 === 0
      const midN = isOdd ? num / 2 : (num + 1) / 2

      var rect = ''

      width = width / num

      let rectX = start[0] - width / 2

      const FIXED_WIDTH = 0 // 柱子间距

      // 数据处理,结构为 { itemStyle: { normal: { color: 'lightgreen' } }, name: '2011', value: [0, 0, 150, 2, 5] }
      // 其中value 分为五个维度,分别为{系列项}(从0开始)、y轴起始值(均为0)、实际值、同系列中索引值(从1开始)、同系列数据项总数

      if (num > 1) {
        if (isOdd) {
          if (currentIndex <= midN) {
            // 中位数左侧
            rectX =
              start[0] - width / 2 - width / 2 + (currentIndex - midN) * width - FIXED_WIDTH * (midN + 1 - currentIndex)
          } else {
            // 中位数右侧
            rectX =
              start[0] - width / 2 + width / 2 + (currentIndex - midN - 1) * width + FIXED_WIDTH * (currentIndex - midN)
          }
        } else {
          rectX = start[0] - width / 2 + (currentIndex - midN) * (width + FIXED_WIDTH)
        }
      }

      rect = {
        type: 'rect',
        shape: echarts.graphic.clipRectByRect(
          { x: rectX, y: end[1], width: width, height: start[1] - end[1] },
          {
            x: params.coordSys.x,
            y: params.coordSys.y,
            width: params.coordSys.width,
            height: params.coordSys.height
          }
        ),
        style: api.style()
      }

      return rect
    }
  },
  mounted() {
    this.lineRender()
  }
}
</script>
<style lang="scss">
#demo {
  width: 800px;
  height: 400px;
  border: 1px solid #000;
}
</style>

该部分代码可以直接在vue中引入后允许,测试数据效果如图:

 

data中的系列项一共分为五个维度,

例如 [0, 0, 120, 1, 4]

分别为{系列项索引值}(从0开始)、y轴起始值(如果是从x轴开始则为0,叠加数值则为实际值如120)、实际y轴值、同系列中索引值(从1开始,表示第几项)、同系列数据项总数

该示例对每个系列有几根柱子判断,判断标准是数据项中的第五个维度值。根据柱子数不一样,分为奇数和偶数个数,两种对坐标的计算方式略有差异,所以分别处理。

坐标的计算方式,在自定义坐标中 params.coordSys 为当前坐标系的包围盒

{
    type: "cartesian2d",
    x: 79.80000000000001,
    y: 60,
    width: 638.4000000000001,
    height: 278
}

坐标系是以左上角为基准,横轴向右为正方向,纵轴向下为正方向,坐标点为{x, y},坐标系的宽高为width height。构成了一整个盒子。

然后就是根据柱子的参数,分为计算出单个柱子的x y width height即可,剩下的就是具体的数学计算逻辑啦,基本还是比较易懂的,就不做过多的描述啦,有问题可以留言交流。 当然计算方式不只一种, 感兴趣的同学可以了解一下。欢迎大家点赞哦~~

 

  • 14
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值