echarts 柱状图y轴(x轴)标签显示头像(图片)

1,078 阅读3分钟

简介

用柱状图显示排名,y轴需要表明排名、头像、名字,如下图 截屏2022-08-25 上午10.27.54.png

查看echarts文档,可是使用如下属性设置图片:

backgroundColor: { 
    image: 'xxx/xxx.png' 
    // 这里可以是图片的 URL, 
    // 或者图片的 dataURI, 
    // 或者 HTMLImageElement 对象, 
    // 或者 HTMLCanvasElement 对象。 
}

注意

|使用 HTMLImageElement 对象时,只能使用标签 |并且在dom元素中是真实存在的,所以使用时需要将其隐藏,并且使用的border-radius: 50%不会生效

<div class="echarts-bar">
    <div ref="barChart" class="bar-box"></div>
    <img ref="imgRef" src="/@/assets/images/header.jpg" class="avatar-img" /> 
  </div>
.avatar-img {
    display: block;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    opacity: 0;
  }
formatter: function (value, index) {
    imgSrc.value = imgs.value[value];
    if (index + 1 == 1) {
         return `{index1|${index + 1}} {a|} ${value}`;
    } else if (index + 1 == 2) {
         return `{index2|${index + 1}} {a|} ${value}`;
    } else if (index + 1 == 3) {
         return `{index3|${index + 1}} {a|} ${value}`;
    } else {
         return `{index|${index + 1}} {a|} ${value}`;
    }
},
rich: {
    index1: {
        fontSize: 20,
        color: 'rgb(242, 165, 11)',
        fontWeight: 500,
    },
    index2: {
        fontSize: 20,
        color: '#ccc',
        fontWeight: 500,
    },
    index3: {
        fontSize: 20,
        color: 'rgb(242, 111, 11)',
        fontWeight: 500,
    },
    index: {
        fontSize: 20,
        color: 'rgb(121, 120, 120)',
        fontWeight: 500,
    },
    a: {
        backgroundColor: {
        image: imgSrc.value, //将图片替换成每个人的头像,并未成功
        // image:'https://xxx/xxx.png', //使用固定的图片地址 可行
        // image: imgRef.value, //使用<img>元素 可行
    },
    height: 20,
    width: 20,
    borderRadius: 10,
    overflow: 'hidden',
  },
}

以上方法,使用固定的图片地址可行,img元素也可行,但是图片并未根据设置的borderRadius: 10而显示为圆形头像

根据杨帆起航_200的方法,对图片进行处理

function getImgData(imgSrc) {
    var fun = function (resolve) {
      const canvas = document.createElement('canvas');
      const contex = canvas.getContext('2d');
      const img = new Image();
      img.crossOrigin = '';
      console.log('contex', contex);
      if (!contex) return;
      img.onload = function () {
        let center = {
          x: img.width / 2,
          y: img.height / 2,
        };
        var diameter = img.width;
        canvas.width = diameter;
        canvas.height = diameter;
        contex.clearRect(0, 0, diameter, diameter);
        contex.save();
        contex.beginPath();
        let radius = img.width / 2;
        contex.arc(radius, radius, radius, 0, 2 * Math.PI); //画出圆
        contex.clip(); //裁剪上面的圆形
        contex.drawImage(
          img,
          center.x - radius,
          center.y - radius,
          diameter,
          diameter,
          0,
          0,
          diameter,
          diameter,
        ); // 在刚刚裁剪的园上画图
        contex.restore(); // 还原状态
        resolve(canvas.toDataURL('image/png', 1));
      };
      img.src = imgSrc;
      // console.log('img.src', img.src);
    };

    var promise = new Promise(fun);

    return promise;
  }

但是此时,图片依旧为只能同一张图片,并不能显示每个人的图像,如果有大佬有可行方法,辛苦留言一下 以下为完整代码:

<template>
  <div class="echarts-bar">
    <div ref="barChart" class="bar-box"></div>
    <img
      ref="imgRef"
      src="/@/assets/images/header.jpg"
      class="avatar-img" />
  </div>
</template>

<script lang="ts" setup>
  import { reactive, ref, watch, onMounted, onBeforeMount, defineComponent, nextTick } from 'vue';
  import * as echarts from 'echarts';
  import { Item } from './data';

  import headerImg from '/@/assets/images/header.jpg';

  const props = defineProps<{
    title: String;
    subtext: String;
    barData: Item[];
    barColor: String;
    gridX: String;
  }>();
  const refData = ref();
  let barChart = ref();
  let xData = ref<any>([]);
  let yData = ref<any>([]);
  let imgs = ref<any>([]);
  let imgSrc = ref<any>();
  let imgRef = ref();

  function dataHandle() {
    xData.value = [];
    yData.value = [];
    props.barData.forEach((item) => {
      yData.value.push(item.name);
      xData.value.push(Number(item.gmv).toFixed(2));
      // imgs.value[item.name] = item.avatarUrl;
      imgs.value.push({ [item.name]: item.avatarUrl });
    });
    getImgsSrc();
    theEcharse();
  }
  async function getImgsSrc() {
    for (let key in imgs.value) {
      imgs.value[key] = await getImgData(imgs.value[key]);
      console.log('hahah', imgs.value[key]);
    }
    // theEcharse();

    console.log('imgs', imgs.value);
  }
  function getImgData(imgSrc) {
    var fun = function (resolve) {
      const canvas = document.createElement('canvas');
      const contex = canvas.getContext('2d');
      const img = new Image();
      img.crossOrigin = '';
      console.log('contex', contex);
      if (!contex) return;
      img.onload = function () {
        let center = {
          x: img.width / 2,
          y: img.height / 2,
        };
        var diameter = img.width;
        canvas.width = diameter;
        canvas.height = diameter;
        contex.clearRect(0, 0, diameter, diameter);
        contex.save();
        contex.beginPath();
        let radius = img.width / 2;
        contex.arc(radius, radius, radius, 0, 2 * Math.PI); //画出圆
        contex.clip(); //裁剪上面的圆形
        contex.drawImage(
          img,
          center.x - radius,
          center.y - radius,
          diameter,
          diameter,
          0,
          0,
          diameter,
          diameter,
        ); // 在刚刚裁剪的园上画图
        contex.restore(); // 还原状态
        resolve(canvas.toDataURL('image/png', 1));
      };
      img.src = imgSrc;
    };

    var promise = new Promise(fun);

    return promise;
  }
  
  async function theEcharse() {
    let chart = barChart.value;
    let myChart;
    nextTick(() => {
      myChart = echarts.init(chart);
      myChart.clear(); // 清除画布
      myChart.setOption(
        (option = {
          tooltip: {},
          legend: {},
          title: {
            text: [`${props.title} {a|(${props.subtext})}`],
            // subtext: props.subtext,
            top: -5,
            textStyle: {
              rich: {
                a: {
                  color: '#bbb',
                  fontSize: 16,
                },
              },
            },
            subtextStyle: {
              fontSize: 16,
            },
          },
          grid: {
            x: props.gridX, //靠左
          },
          yAxis: {
            type: 'category',
            inverse: true, //排序
            data: yData.value,
            z: 4,
            axisLabel: {
              show: true,
              fontSize: 14,
              textStyle: {
                align: 'left', //**
              },
              margin: 70, //刻度标签与轴线之间的距离。
              formatter: function (value, index) {
                imgSrc.value = imgs.value[value];
                console.log('imgSrc.value', imgSrc.value);
                if (index + 1 == 1) {
                  return `{index1|${index + 1}} {a|} ${value}`;
                } else if (index + 1 == 2) {
                  return `{index2|${index + 1}} {a|} ${value}`;
                } else if (index + 1 == 3) {
                  return `{index3|${index + 1}} {a|} ${value}`;
                } else {
                  return `{index|${index + 1}} {a|} ${value}`;
                }
              },
              rich: {
                index1: {
                  fontSize: 20,
                  color: 'rgb(242, 165, 11)',
                  fontWeight: 500,
                },
                index2: {
                  fontSize: 20,
                  color: '#ccc',
                  fontWeight: 500,
                },
                index3: {
                  fontSize: 20,
                  color: 'rgb(242, 111, 11)',
                  fontWeight: 500,
                },
                index: {
                  fontSize: 20,
                  color: 'rgb(121, 120, 120)',
                  fontWeight: 500,
                },
                a: {
                  backgroundColor: {
                     image: imgSrc.value,
                    // image:
                    //   'https://xxx/xxx.png',
                    // image: imgRef.value,
                  },
                  height: 20,
                  width: 20,
                  borderRadius: 10,
                  overflow: 'hidden',
                },
              },
            },
          },
          xAxis: {
            axisLabel: {
              //  X 坐标轴标签相关设置,写在xAxis里面
              interval: 0, //全部标签显示
              rotate: '45', //标签倾斜度数
            },
            splitLine: {
              show: true,
              lineStyle: {
                type: 'dashed',
              },
            },
          },
          series: [
            {
              realtimeSort: true,
              name: '',
              type: 'bar',
              data: xData.value,
              color: props.barColor,
              barCategoryGap: '60%',
              // barWidth: '30px',
              label: {
                show: true,
                position: 'right',
                valueAnimation: true,
              },
              itemStyle: {
                color: props.barColor,
              },
            },
          ],
          legend: {
            show: false,
          },
        }),
      );
    });
    var option;
    option && myChart.setOption(option);
    // flag 阻止重复添加事件
    // if (this.flag) return;
    window.addEventListener('resize', () => {
      myChart.resize();
    });
    // this.flag = true;
  }
  onBeforeMount(() => {});
  onMounted(() => {
    dataHandle();
  });
</script>
<style lang="less" scoped>
  .echarts-bar {
    width: 100%;
    height: 100%;
    padding: 0.1rem;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    .bar-box {
      width: 100%;
      height: 100%;
    }
  }
</style>


如果想让y轴文字有背景颜色,使用pading给文字增加内边距,如果想要分割线,使用borderWidthwidth,如果想换行,使用\n

代码如下

axisLabel: {
           show: true,
           fontSize: 14,
           textStyle: {
             align: 'right', //**
           },
           margin: 70, //刻度标签与轴线之间的距离。
           formatter: function (value, index) {
             // return `{index|${index + 1}} ${value}`;
             return '{a|{a}}{abg|}\n{hr|}\n  {b|{b}:}{c}  {per|{d}%}  ';
           },
           rich: {
             index: {
               fontSize: 16,
               color: '#fff',
               fontWeight: 600,
               padding: [3, 6, 3, 6], //文字块内边距,[3,4,3,4]
               borderRadius: 16,
               backgroundColor: '#1F54D1',
             },
             a: {
               //标题名的样式
               color: '#6E7079',
               lineHeight: 22, //标签的表格的上高度
               align: 'center', //文字水平对齐方式
             },
             hr: {
               //分割线
               borderColor: '#8C8D8E',
               width: '100%',
               borderWidth: 1,
               height: 0,
             },
             b: {
               //数据名的样式
               color: '#4C5058',
               fontSize: 14,
               fontWeight: 'bold',
               lineHeight: 33, //表格高度,行高
             },
             per: {
               //自定义百分比的样式
               color: '#fff',
               backgroundColor: '#4C5058',
               padding: [3, 4], //文字块内边距,[3,4,3,4]
               borderRadius: 4,
             },
           },
         },

效果图如下:

截屏2022-09-23 上午11.50.34.png


需求变更:排名需要加一个蓝色背景圆 如果使用上面方法给文字加内边距,由于文字字体大小不一样(不是指font-size),例如:大小不明显的1和6,明显的1和10,所以每个数字的背景并不是都是标准圆,所以采用背景图片的方法

代码如下:直接使用地址时,本地可以正常显示,但是打包到显示时,背景图片不显示,且显示请求背景图片资源报错 猜测原因:打包之后,image: '/@/assets/images/echarts-circle.png'被认为是线上图片,所以会到https://xxxx.cn/assets/images/echarts-circle.png请求图片

rich: {
   index: {
        fontSize: 16,
        color: '#fff',
        align: 'center',
        fontWeight: 600,
        // padding: [3, 6.5, 3, 6.5], //文字块内边距,[3,4,3,4]
        borderRadius: 16,
        // backgroundColor: '#1F54D1',
        backgroundColor: {
           image: '/@/assets/images/echarts-circle.png',
        },
        height: 25,
        width: 25,
        overflow: 'hidden',
    },
},

改为如下代码即可

import bgImg from '/@/assets/images/echarts-circle.png';
......
rich: {
   index: {
        fontSize: 16,
        color: '#fff',
        align: 'center',
        fontWeight: 600,
        // padding: [3, 6.5, 3, 6.5], //文字块内边距,[3,4,3,4]
        borderRadius: 16,
        // backgroundColor: '#1F54D1',
        backgroundColor: {
           image: bgImg,
        },
        height: 25,
        width: 25,
        overflow: 'hidden',
    },
},