基于echart关系图自定义图片,圆角,描边和阴影

1,080 阅读5分钟

基于echart绘制关系图,翻了下echart type="graph" layout="force"时对节点进行圆角,描边和投影这些很简单的效果,确不支持,后面尝试了几种解决方法,一种是创建dom,写好样式后再生成图片,后面测试了又很大的性能问题,最后确定用图片直接基于canvas绘制好想要的效果后,直接传给echart,性能快了不少。 下面是效果图:

image.png

 nodeData: {
        links: [
          //关系数据
          { source: "1002", target: "1001", isShortest: 1 },
          { source: "1003", target: "1001", isShortest:false },
          { source: "1004", target: "1001", isShortest:false },
          { source: "1005", target: "1001", isShortest: 1 },
          { source: "1006", target: "1001", isShortest: 1 },
          { source: "1007", target: "1001", isShortest: 1 },
          { source: "1008", target: "1001", isShortest:false },
          { source: "1009", target: "1001", isShortest: 1 },
          { source: "1010", target: "1001", isShortest: false },
          { source: "1011", target: "1001", isShortest: 1 },
          { source: "1004", target: "1003", isShortest: 1 },
          { source: "1007", target: "1003", isShortest: 1 },
          { source: "1007", target: "1004", isShortest: 1 },
          { source: "1007", target: "1005", isShortest: false },
          { source: "1007", target: "1006", isShortest: false },
          { source: "1008", target: "1007", isShortest: false },
          { source: "1008", target: "1004", isShortest: false },
          { source: "1010", target: "1007", isShortest: false },
        ],
        nodes: [
          // type:0 客户 1:员工
          {
            name: "张三",
            id: "1001",
            avatar: require("@/assets/txTest.jpg"),
            type: 1,
            isBig: true,
            isShow: true,
          },
          {
            name: "李四",
            id: "1002",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: false,
          },
          {
            name: "王五",
            id: "1003",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: false,
          },
          {
            name: "李四李四光光",
            id: "1004",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: false,
          },
          {
            name: "赵六",
            id: "1005",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: false,
          },
          {
            name: "田七",
            id: "1006",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: true,
          },
          {
            name: "王八",
            id: "1007",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: true,
          },
          {
            name: "赵九",
            id: "1008",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: true,
          },
          {
            name: "吴文华",
            id: "1009",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: true,
          },
          {
            name: "武松",
            id: "1010",
            avatar: require("@/assets/txTest.jpg"),
            type:0,
            isBig: false,
            isShow: true,
          },
          {
            name: "孙武",
            id: "1011",
            avatar: require("@/assets/txTest.jpg"),
            type: 0,
            isBig: false,
            isShow: true,
          },
        ],
      },

下面是js部分: 1.首先调用 this.startImg();方法

startImg(json) {
      let androidMap = this.nodeData.nodes;
      let that = this;
      let picList = []; //获取出全部图片
      for (let i = 0; i < androidMap.length; i++) {
        //把图片路径转成canvas
        let p = this.getImgData(androidMap[i].avatar,androidMap[i]);

        picList.push(p);
      }

      Promise.all(picList).then(function (images) {
        //取出base64 图片 然后赋值到jsondata中
        for (let i = 0; i < images.length; i++) {
          // var img = "image://" + images[i];
          var img = images[i];
          androidMap[i].avatar = img;
        }
        that.nodeData.nodes = androidMap;
        that.init();
      });
    },

2.在startImg()方法里面调用getImgData()方法,传入图片链接和当前item的数据 ,这个方法会把图片进行canvas绘制,描边,阴影,圆角都在这个方法里面完成

 getImgData(imgSrc,item) {
      //将网络图描边
      let radius, diameter, canvas, ctx,color="#F3F3F3";
      if(item.isShow && item.type == 0){//如果是客户,且又是最短路径
        color='#FF5153'
      }
      if(item.type == 1){//如果是员工
        color='#0083FF'
      }
      let img = new Image();
      img.setAttribute("crossOrigin", "anonymous"); // 解决图片跨域访问失败
      img.src = imgSrc;
      return new Promise((resolve) => {
        img.addEventListener(
          "load",
          () => {
            let { width, height } = img;
            if (img.width > img.height) {
              radius = height / 2;
            } else {
              radius = width / 2;
            }
            diameter = radius * 2;
            canvas = document.createElement("canvas");
            if (!canvas.getContext) {
              // 判断浏览器是否支持canvas,如果不支持在此处做相应的提示
              console.log("您的浏览器版本过低,暂不支持。");
              return false;
            }
            canvas.width = diameter;
            canvas.height = diameter;

            ctx = canvas.getContext("2d");
            ctx.translate(0.5, 0.5); //使用translate(0.5,0.5)去掉锯齿
            ctx.clearRect(0, 0, diameter, diameter);
            //投影
            ctx.shadowOffsetX = 0;
            (ctx.shadowOffsetY = 6), (ctx.shadowColor = "rgba(38,38,38,0.4)");
            ctx.shadowBlur = 50;
            // 描边
            ctx.save(); //save和restore可以保证样式属性只运用于该段canvas元素
            ctx.strokeStyle =color; //设置描边的边线的颜色
            ctx.lineWidth = 40;
            ctx.beginPath(); //开始路径
            ctx.arc(radius, radius, radius - 38, 0, Math.PI * 2); //画一个整圆.
            ctx.stroke(); //绘制边线

            // 截圆形图
            ctx.save();
            ctx.beginPath();
            ctx.arc(radius, radius, radius - 50, 0, Math.PI * 2);
            ctx.clip();

            let x = 0,
              y = 0,
              swidth = diameter,
              sheight = diameter;

            ctx.drawImage(img, x, y, swidth, sheight, 0, 0, diameter, diameter);
            ctx.restore();
            // toDataURL()是canvas对象的一种方法,用于将canvas对象转换为base64位编码
            let dataURL = canvas.toDataURL("image/png");
            resolve(dataURL);
          },
          false
        );
      });
    },

3.startImg()方法处理完图片后会调用echart的触发化 init()方法,这个init()方法会给echart的option配置做一些处理,然后去渲染关系图画布

 init() {
      const option = {
        animationDurationUpdate: 1500,
        animationEasingUpdate: "quinticInOut",
        series: [
          {
            type: "graph",
            layout: "force",
            force: {
              //力引导布局相关的配置项
              repulsion: 1000, //相距距离
               gravity:0.01,//
              layoutAnimation: true,
              edgeLength: [100, 150], //边的两个节点之间的距离,这个距离也会受 repulsion。
            },
         
            zoom:0.6,//当前视角的缩放比例
            legendHoverLink: true, //是否启用图例 hover(悬停) 时的联动高亮。
            draggable: true, //节点是否可拖拽,只在使用力引导布局的时候有用。
            focusNodeAdjacency: true, //是否在鼠标移到节点上的时候突出显示节点以及节点的边和邻接节点。
            // edgeSymbol:["circle", "arrow"],//边两边的类型

            markPoint: "rect",
            autoCurveness: 0.01, //多条边的时候,自动计算曲率
            edgeLabel: {
              //边的设置
              show: false,
              position: "middle",
              fontSize: 12,
              formatter: (params) => {
                // debugger
                // return params.data.relation.name;
                // return "333";
              },
            },
            edgeSymbol: ["pin", "pin"], //边两边的类型 circle:圆 rect 正方形 roundRect 圆角正方形 triangle  三角形  diamond 菱形 pin 饼图 arrow:
            edgeSymbolSize: 8, //边两端的标记大小,可以是一个数组分别指定两端,也可以是单个统一指定。 
            // symbolOffset: [-10 ,-30,], //['10%','-100%'], //边两边的距离 
            // symbolSize:[50,20],//图形大小
            roam: true, //是否开启鼠标缩放和平移漫游
            nodeScaleRatio: 0.6, //鼠标漫游缩放时节点的相应缩放比例,当设为0时节点不随着鼠标的缩放而缩放
            data: this.nodeData.nodes.map(function (node) {
              let newSetData = {
                bg: "#F3F3F3",
                color: "#333333",
                size: [37, 37],
              };
              if (node.type == 1) {
                newSetData = {
                  bg: "#0098FF",
                  color: "#FFFFFF",
                };
              } 
              if (node.isBig) {
                newSetData.size = [60, 60];
              }
              if (node.isShow && node.type == 0) {
                newSetData.bg="#FF4D4F";
                newSetData.color="#ffffff";
              }
              return {
                id: node.id,
                name: node.name,
                symbol: "image://" + node.avatar,
                symbolSize: newSetData.size,
                 
                symbolOffset: ['-0%' ,"-80%","0",'30%' ],
                // symbolSize: node.value,//点大小
                itemStyle: {
                  color: newSetData.bg,
                  borderWidth: "1px",
                  borderType: "dashed",
                  borderColor: newSetData.color,
                  borderRadius: [14, 14, 14, 14],
                  borderCap: "round",
                },
                label: {
                  //图形上文本标签
                  // offset:[10,0],
                  // formatter:(params) => { 
                  //   // return params.data.relation.name;
                  //   return "222";
                  // },
                  // rich:{
                  //   a: {
                  //       color: 'red',
                  //       lineHeight: 10
                  //   },
                  //   b: {
                  //       backgroundColor: {
                  //           image: node.avatar
                  //       },
                  //       height: 40
                  //   },  
                  // },
                  normal: {
                    show: true,
                    position: "bottom",
                    textStyle: {
                      color: newSetData.color, //字体颜色
                      fontStyle: "normal", //文字字体的风格 'normal'标准 'italic'斜体 'oblique' 倾斜
                      fontWeight: "normal", //'normal'标准'bold'粗的'bolder'更粗的'lighter'更细的或100 | 200 | 300 | 400...
                      fontFamily: "sans-serif", //文字的字体系列
                      fontSize: 12, //字体大小,
                      backgroundColor: newSetData.bg,
                      padding: [6, 4, 3, 4],
                      borderRadius: [4, 4, 4, 4],
                    },
                  },
                },
              };
            }),
            edges: this.nodeData.links.map(function (edge) {
              let lineData = {
                color: "#858585",
                type: "dotted",
                width: 1,
              };
              if (edge.isShortest) {
                lineData = {
                  color: "#FF4D4F",
                  type: "solid",
                  width: 2,
                };
              }
              return {
                source: edge.source,
                target: edge.target,
                lineStyle: {
                  //==节点连线样式
                   dashOffset:'10',
                   join:'round',
                  width: lineData.width,
                  type: lineData.type, //线的类型 'solid'(实线)'dashed'(虚线)'dotted'(点线)
                  color: lineData.color,
                  curveness: 0.01, //线条的曲线程度,从0到1
                  opacity: 0.7,
                },
              };
            }),
            emphasis: {
              //高亮状态的图形样式。
              focus: "adjacency",
              scale: 1.5, //放大倍数
              label: {
                // position: 'buttom',
                show: true,
              },
              lineStyle: {
                color: "rgba(0,0,0,0.3)",
                width: 2,
                type: "solid",
                dashOffset: 5,
              },
            },
          },
        ],
      };
      this.drawInit(option);
    }

4.执行drawInit()方法渲染画布

drawInit(option) {
      const that = this;
      that.$nextTick(() => {
        const dom = document.getElementById("relationalChart");
        if (dom) {
          this.loadingShow=false 
          const myChart = echarts.init(dom);
          myChart.clear(); // 必写,清空数据,重新赋值
          myChart.setOption(option);
        }
      });
    }

以上就是画布的代码有问题,或者有更好的处理方式可以WX交流: hongchen0319