echarts中自定义图表实现,包含双y轴对齐,X轴置于最底部,自定义标注,且y轴从最小值开始向上而不是从零开始

4,149 阅读7分钟

效果图

效果图.jpg

需求

实现一个移动端的图表,要求双y轴两端刻度值要对齐,其中有一条柱子是虚线,且虚线上方有圆环标注,y轴起始位置是最小值而不是0

实现思路

注:这个图唯一复杂的地方就是y轴不是从0开始,而是从最小值开始。(后附源码)

首先实现柱状图y轴刻度两端对齐

先实现两个y轴,这个一般很简单,我们只需要在yAxis中设置两个配置项就OK啦

然后实现两端刻度值(也就是y轴刻度线)对齐我们需要用到下面几个属性

yAxis:[{
    min:number string Function;
    max:number string Function;
    interval:number;  // 强制设置坐标轴分割间隔。
    splitNumber:number; //坐标轴的分割段数,需要注意的是这个分割段数只是个预估值,最后实际显示    的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整。
},
{
    min:number string Function;
    max:number string Function;
    interval:number;  // 强制设置坐标轴分割间隔。
    splitNumber:number; //坐标轴的分割段数,需要注意的是这个分割段数只是个预估值,最后实际显示    的段数会在这个基础上根据分割后坐标轴刻度显示的易读程度作调整。
}]

因为是两个Y轴,所以两个配置项都要写,最大值和最小值为所有到这个坐标轴所有数据的最大值和最小值,例如:

var arr1 = [1,2,3]
var arr2 = [3,4,5]

这两组数据使用的是左边的y轴,那么左边Y轴的最大值和最小值就是1和5。

 splitNumber这个属性有时候设置了其实并不好使,所以我们就直接用interval,强制将y轴刻度等分

const interval_Y1 = parseFloat(((Max_Y1 - Min_Y1) / 4).toFixed(3));
const interval_Y2 = parseFloat(((Max_Y2 - Min_Y2) / 4).toFixed(3));

用最大值减去最小值就是y轴的最大长度然后想几等分就除以几,上面是四等分,有时候分的太多也会导致精度丢失而使得两边y轴对不齐。根据大家需求自行调整。

做到这一步的话,两边的Y轴基本上就对齐了,但是如果数据中出现负数的话,就会产生数据从0刻度线向下生成图表的情况,那就和我们的需求不相符了,而且x轴会出现在0刻度的地方,

要解决x轴在0刻度的问题,我们只需要设置xAxis.axisLine.onZero = false 就ok啦。

最后要解决的问题就是数据从最小值向上生成的问题了,在echerts5.5.1版本上有startValue这个配置项我们只需要在这设置最小值就OK了,虚线和标注直接用pictorialBar和markPoint这两个配置项就能解决。我们项目使用的是4.5.5,所有不能用这个配置项。

我的解决方法就只能用custom自定义图像渲染逻辑。

首先我们介绍一下我们用到的配置项

//官网上抄的 https://echarts.apache.org/zh/option.html#series-custom.type
//custom 系列需要开发者自己提供图形渲染的逻辑。这个渲染逻辑一般命名为 renderItem。例如:
var option = {
    ...,
    series: [{
        type: 'custom',
        renderItem: function (params, api) {
            var categoryIndex = api.value(0);
            var start = api.coord([api.value(1), categoryIndex]);
            var end = api.coord([api.value(2), categoryIndex]);
            var height = api.size([0, 1])[1] * 0.6;

            var rectShape = echarts.graphic.clipRectByRect({
                x: start[0],
                y: start[1] - height / 2,
                width: end[0] - start[0],
                height: height
            }, {
                x: params.coordSys.x,
                y: params.coordSys.y,
                width: params.coordSys.width,
                height: params.coordSys.height
            });

            return rectShape && {
                type: 'rect',
                shape: rectShape,
                style: api.style()
            };
        },
        data: data
    }]
}
//官网上抄的 https://echarts.apache.org/zh/option.html#series-custom.type
//其中配置项上的内容太多了,大家可以去官网上查看,

其中那条虚线和标注图像我是用两个配置项做的,因为实在想不到什么好办法了

下面把源码贡献给大家,其中有些垃圾数据,之前调的时候随便写的,太懒就没删

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>国内生产总值及增速</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@4.5.0/dist/echarts.min.js"></script>
  </head>

  <body>
    <div id="lc" style="width: 800px; height: 500px"></div>
    <script>
      var img = "./img/椭圆形备份.png";//此图片为自定义标注的图片,可以根据需求自行修改
      var myecharts = echarts.init(document.getElementById("lc"));
      var dataList = [
        {
          name: "2014",
          value1: 200,
          value2: 673,
          value3: 586,
          xAxis: 0,
          yAxis: 202,
        },
        {
          name: "2015",
          value1: 703,
          value2: 673,
          value3: 603,
          xAxis: 1,
          yAxis: 705,
        },
        {
          name: "2016",
          value1: -666,
          value2: -588,
          value3: -638,
          xAxis: 2,
          yAxis: 890,
        },
        {
          name: "2017",
          value1: 752,
          value2: 782,
          value3: 682,
          xAxis: 3,
          yAxis: 755,
        },
        {
          name: "2018",
          value1: 779,
          value2: 779,
          value3: 679,
          xAxis: 4,
          yAxis: 782,
        },
        {
          name: "2019",
          value1: 785,
          value2: 855,
          value3: 685,
          xAxis: 5,
          yAxis: 787,
        },
      ];
      // y轴最大值最小值
      var dataList_y1 = [];
      dataList.forEach((item) => {
        dataList_y1.push(item.value2, item.value3);
      });
      var dataList_y2 = dataList.map((item) => item.value1);
      var Max_Y1 = Math.max(...dataList_y1) + 20;
      var Min_Y1 = Math.min(...dataList_y1) - 20;
      var Max_Y2 = Math.max(...dataList_y2) + 20;
      var Min_Y2 = Math.min(...dataList_y2) - 20;
      const interval_Y1 = parseFloat(((Max_Y1 - Min_Y1) / 4).toFixed(3));
      const interval_Y2 = parseFloat(((Max_Y2 - Min_Y2) / 4).toFixed(3));
      option = {
        grid: {
          top: "70",
          left: "30",
          right: "10",
          bottom: "30",
          containLabel: true,
        },
        tooltip: {
          trigger: "axis",
          axisPointer: {
            type: "none",
          },
        },
        legend: {
          type: "scroll",
          right: "center",
          top: "0",

          data: [
            { name: "上年" },
            { name: "本年" },
            {
              icon: "image://" + "./img/编组 25@3x.png",
              name: "同比",
              itemHeight: 157,
            },
          ],
          itemGap: 20,
          itemWidth: 13,
          itemHeight: 13,
          textStyle: {
            fontSize: "13",
            color: "#96999C",
          },
        },
        xAxis: [
          {
            data: dataList.map((item) => item.name),
            dataMin: -Infinity,
            axisLabel: {
              rotate: -45,
              color: "#96999C",
              textStyle: {
                fontSize: 8,
              },
            },
            axisLine: {
              onZero: false,
            },
            axisTick: {
              show: false,
            },
          },
        ],
        yAxis: [
          {
            type: "value",
            scale: true,
            max: Max_Y1,
            min: Min_Y1,
            interval: interval_Y1,
            splitNumber: 4,
            // name: "亿元",
            nameTextStyle: {
              color: "#96999C",
              fontSize: 8,
            },
            axisLine: {
              show: false,
            },
            axisLabel: {
              color: "#96999C",
              formatter: "{value} 亿元",
              textStyle: {
                fontSize: 8,
              },
            },
            axisTick: {
              show: false,
            },
            splitLine: {
              lineStyle: {
                type: "dashed",
                color: "rgba(62,91,125,.25)",
              },
            },
          },
          {
            max: Max_Y2,
            min: Min_Y2,
            interval: interval_Y2,
            scale: true,
            splitNumber: 4,
            type: "value",
            axisLine: {
              show: false,
            },
            axisLabel: {
              formatter: "{value} ",
              textStyle: {
                fontSize: 8,
              },
            },
            axisTick: {
              show: false,
            },
            splitLine: {
              lineStyle: {
                type: "dashed",
                color: "rgba(62,91,125,.25)",
              },
            },
          },
        ],
        series: [
          {
            name: "上年",
            type: "custom",
            yAxisIndex: 0,
            renderItem: function (params, api) {
              var categoryIndex = api.value(0);
              var value = api.value(1);
              var start = api.coord([categoryIndex, Min_Y1]);
              var end = api.coord([categoryIndex, value]);
              var width = api.size([1, 0])[0] * 0.1; // 柱子的宽度可以设置为类目宽度的60%
              // 绘制矩形(柱状图)
              var rectShape = echarts.graphic.clipRectByRect(
                // 矩形的位置和大小
                {
                  x: start[0] - width / 2 - 20,
                  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,
                }
              );
              if (rectShape) {
                rectShape.r = [20, 20, 0, 0];
              }
              return (
                // 这里返回为这个 dataItem 构建的图形元素定义。
                rectShape && {
                  type: "rect", // 表示这个图形元素是矩形。还可以是 'circle', 'sector', 'polygon' 等等。
                  transition: ["shape"],
                  shape: rectShape,
                  style: api.style(), // 用 api.style(...) 得到默认的样式设置。这个样式设置包含了option 中 itemStyle 的配置和视觉映射得到的颜色。
                }
              );
            },
            data: dataList.map((item) => item.value2),
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(
                  0,
                  0,
                  0,
                  1,
                  [
                    {
                      offset: 0,
                      color: "#4EDB67 ",
                    },
                    {
                      offset: 1,
                      color: "#9BFB7D",
                    },
                  ],
                  false
                ),
                barBorderRadius: [5, 5, 0, 0],
              },
            },
            silent: true,
            tooltip: {
              formatter: function (params) {
                return params.name + ": " + params.value;
              },
            },
          },
          {
            name: "本年",
            type: "custom",
            yAxisIndex: 0,
            renderItem: function (params, api) {
              var categoryIndex = api.value(0);
              var value = api.value(1);
              var start = api.coord([categoryIndex, Min_Y1]);
              var end = api.coord([categoryIndex, value]);
              var width = api.size([1, 0])[0] * 0.1;
              var rectShape = echarts.graphic.clipRectByRect(
                // 矩形的位置和大小
                {
                  x: start[0] - width / 2 + 20,
                  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,
                }
              );
              if (rectShape) {
                rectShape.r = [20, 20, 0, 0];
              }
              return (
                rectShape && {
                  type: "rect",
                  transition: ["shape"],
                  shape: rectShape,
                  style: api.style(),
                }
              );
            },
            data: dataList.map((item) => item.value3),
            itemStyle: {
              normal: {
                color: new echarts.graphic.LinearGradient(
                  0,
                  0,
                  0,
                  1,
                  [
                    {
                      offset: 0,
                      color: "#096CD3",
                    },
                    {
                      offset: 1,
                      color: "#33B8FF",
                    },
                  ],
                  false
                ),
              },
            },
            silent: true,
            tooltip: {
              formatter: function (params) {
                return params.name + ": " + params.value;
              },
            },
          },
          {
            name: "同比",
            type: "custom",
            yAxisIndex: 1,
            renderItem: function (params, api) {
              console.log(params);
              var categoryIndex = api.value(0);
              console.log(api.value(0));
              var value = api.value(1);
              var start = api.coord([categoryIndex, Min_Y2]);
              var end = api.coord([categoryIndex, value]);
              console.log(start, end);
              var width = api.size([1, 0])[0] * 0.1;
              console.log(start, "start");
              return {
                type: "group",
                children: [
                  {
                    type: "line",
                    shape: {
                      x1: start[0],
                      y1: start[1],
                      x2: end[0],
                      y2: end[1],
                    },
                    style: {
                      fill: "transparent",
                      stroke: "#FBAB1E",
                      lineDash: [5, 5],
                      lineWidth: 2,
                    },
                  },
                ],
              };
            },
            data: dataList.map((item) => item.value1),
            silent: true,
            tooltip: {
              formatter: function (params) {
                return params.name + ": " + params.value;
              },
            },
          },
          {
            name: "同比",
            type: "custom",
            yAxisIndex: 1,
            renderItem: function (params, api) {
              console.log(params);
              var categoryIndex = api.value(0);
              console.log(api.value(0));
              var value = api.value(1);
              var start = api.coord([categoryIndex, Min_Y2]);
              var end = api.coord([categoryIndex, value]);
              console.log(start, end);
              var width = api.size([1, 0])[0] * 0.1;
              console.log(start, "start");
              return {
                type: "group",
                children: [
                  {
                    type: "image",
                    style: {
                      image: "./img/椭圆形备份.png",
                      x: end[0] - 15,
                      y: end[1] - 20,
                      width: 30,
                      height: 30,
                    },
                  },
                ],
              };
            },
            data: dataList.map((item) => item.value1),
            // 比如,如果你不想在点击柱子时触发任何事件,可以设置 silent: true
            silent: true,
            tooltip: {
              show: false,
            },
          },
        ],
      };
      console.log(option);
      myecharts.setOption(option);
    </script>
  </body>
</html>