d3.js 實(shí)現(xiàn)企業(yè)圖譜(基于vue)

背景

前段時(shí)間接到公司需求,參考企查查開(kāi)發(fā)=>企業(yè)圖譜孤个。最開(kāi)始定的是使用Relation Graph實(shí)現(xiàn)剃允,用了一段時(shí)間后發(fā)現(xiàn)這個(gè)插件對(duì)于一些個(gè)性化的需求支持不足,無(wú)奈只能另辟新徑齐鲤,后來(lái)發(fā)現(xiàn)了d3.js這個(gè)新大陸斥废。
由于這是第一次接觸d3,剛開(kāi)始真是一臉懵,后來(lái)通過(guò)翻閱資料给郊,先是了解了一下svg牡肉,然后又把d3.js常用的一些api看了看算是有個(gè)初步的了解。

技術(shù)點(diǎn)

使用d3主要需要掌握svg淆九,jquery和d3這幾個(gè)知識(shí)點(diǎn)统锤。
本次使用的是 d3.v5版本的。

案例

company.png

實(shí)現(xiàn)效果

qcx.png

實(shí)現(xiàn)代碼

HTML

<template>
  <div class="tree04">
    <div class="seeTree-page" id="app">
      <div id="treeRoot"></div>
    </div>
  </div>
</template>

JS

<script>
import companyJson from "./DLJSON/companyJson.json";
import $ from "./js/jquery-3.1.1.min";
import * as d3 from "./js/d3.v5";
let svg;
let dirRight;
let forUpward;
let leng;
// var u = navigator.userAgent
//   app = navigator.appVersion;
// var isAndroid = u.indexOf("Android") > -1 || u.indexOf("Linux") > -1; //g
// var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios終端
// var ua = navigator.userAgent.toLowerCase();
// var ualower = navigator.userAgent.toLocaleLowerCase();
// //var treeData =
// var width = document.body.clientWidth,
//   height = document.body.clientHeight;
let toggle = true;
let circlewidth1;
let circlewidth2;
let circlewidth3;
let margin1 = { top: 50, right: 20, bottom: -20, left: 0 };
export default {
  data() {
    return {
      container: null, //容器svg>g
      duration: 500, //動(dòng)畫(huà)持續(xù)時(shí)間
      scaleRange: [0.2, 2], //container縮放范圍
      direction: ["r", "l"], //分為左右2個(gè)方向
      centralPoint: [0, 0], //畫(huà)布中心點(diǎn)坐標(biāo)x,y
      root: { r: {}, l: {} }, //左右2塊數(shù)據(jù)源
      rootNodeLength: 0, //根節(jié)點(diǎn)名稱(chēng)長(zhǎng)度
      rootName: ["昆侖數(shù)智", ""], //根節(jié)點(diǎn)名稱(chēng)
      textSpace: 15, //多行文字間距
      themeColor: "#2196F3", //主色
      nodeSize: [50, 100], //節(jié)點(diǎn)間距(高/水平)
      fontSize: 14, //字體大小炭庙,也是單字所占寬高
      rectMinWidth: 50, //節(jié)點(diǎn)方框默認(rèn)最小饲窿,
      textPadding: 5, //文字與方框間距,注:固定值5
      circleR: 5, //圓圈半徑
      dirRight: "",
      treeData: [],
    };
  },
  created() {},
  mounted() {
    this.getData();
  },
  computed: {
    treeMap() {
      return d3
        .tree()
        .nodeSize(this.nodeSize)
        .separation((a, b) => {
          let result =
            a.parent === b.parent && !a.children && !b.children ? 1 : 2;
          return result;
        });
    },
  },
  watch: {},
  methods: {
    getData() {
      // var mynodes;
      // $.ajax({
      //     url: "first.json",
      //     contentType: "application/json;charset=UTF-8",
      //     type: "POST",
      //     dataType: "json",

      //     success: function (data) {
      this.rootName = [companyJson.data.rootName, ""];
      let data = companyJson.data;
      console.log(data, "原始數(shù)據(jù)");
      let left = data.l;
      let right = data.r;
      let mynodes;
      for (var i = 0; i < left.children.length; i++) {
        if (left.children[i].children.length > 2) {
          mynodes = left.children[i].children;
          left.children[i].children = left.children[i].children.slice(0, 2);
          left.children[i].children[2] = {
            name: "更多",
            type: -1,
            val: mynodes.length - 2,
            childrend: mynodes.slice(0, mynodes.length),
          };
        }
      }
      for (var i = 0; i < right.children.length; i++) {
        if (right.children[i].children.length > 2) {
          mynodes = right.children[i].children;
          right.children[i].children = right.children[i].children.slice(0, 2);
          right.children[i].children[2] = {
            name: "更多",
            type: -1,
            val: mynodes.length - 2,
            childrend: mynodes.slice(0, mynodes.length),
          };
        }
      }
      // console.log(data, "初始化");
      this.treeInit(data);
      //     }
      // })
    },
    uuid() {
      function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
          .toString(16)
          .substring(1);
      }
      return (
        s4() +
        s4() +
        "-" +
        s4() +
        "-" +
        s4() +
        "-" +
        s4() +
        "-" +
        s4() +
        s4() +
        s4()
      );
    },
    //初始化
    treeInit(data) {
      const margin = { top: 0, right: 0, bottom: 0, left: 0 };
      let treeWidth = d3.select("#treeRoot")._parents[0].clientWidth;
      let treeHeight = d3.select("#treeRoot")._parents[0].clientHeight;
      // const treeWidth = document.body.clientWidth - margin.left - margin.right; //tree容器寬
      // const treeHeight = document.body.clientHeight - margin.top - margin.bottom; //tree容器高
      const centralY = treeWidth / 2 + margin.left;
      const centralX = treeHeight / 2 + margin.top;
      this.centralPoint = [centralX, centralY]; //中心點(diǎn)坐標(biāo)

      //根節(jié)點(diǎn)字符所占寬度
      this.rootNodeLength = this.rootName[0].length * this.fontSize + 50;

      //svg標(biāo)簽
      svg = d3
        .select("#treeRoot")
        .append("svg")
        .attr("class", "tree-svg")
        .attr("xmlns", "http://www.w3.org/2000/svg")
        .attr("width", treeWidth)
        .attr("height", treeHeight)
        .attr("font-size", this.fontSize)
        .attr("fill", "#555");

      //g標(biāo)簽
      this.container = svg
        .append("g")
        .attr("class", "container1")
        .attr(
          "transform",
          "translate(" + margin1.left + "," + margin1.top + ")scale(0.8)"
        );

      //畫(huà)出根節(jié)點(diǎn)
      this.drawRoot();
      //指定縮放范圍
      const zoom = d3
        .zoom()
        .scaleExtent(this.scaleRange)
        .on("zoom", this.zoomFn);
      //動(dòng)畫(huà)持續(xù)時(shí)間
      this.container
        .transition()
        .duration(this.duration)
        .call(zoom.transform, d3.zoomIdentity);
      svg.call(zoom);
      //數(shù)據(jù)處理
      // console.log(data, "數(shù)據(jù)處理");
      this.dealData(data);
    },

    zoomFn() {
      var weizhi = document.getElementsByClassName("container1")[0];
      weizhi.style.transform = ""; //偏移位置
      let zoom = d3.event.transform;
      return this.container.attr(
        "transform",
        "translate(" +
          (Number(zoom.x) + Number(margin1.left)) +
          "," +
          (zoom.y + margin1.top) +
          ")scale(" +
          zoom.k * 0.8 +
          ")"
      );
    },
    //數(shù)據(jù)重組
    DataReor(d, direction, source, appendData) {
      var setDepth = function (node, num, appendLeaf) {
        //重新設(shè)置depth
        node.depth = num;
        if (node.children && node.children.length) {
          //遍歷children
          node.children.forEach(function (item) {
            setDepth(item, num + 1, appendLeaf);
          });
        } else {
          appendLeaf.push(node);
        }
      };

      var setHeight = function (arr, num) {
        //重新設(shè)置height
        var parent = [];
        arr.forEach(function (node) {
          node.height = Math.max(num, node.height);
          if (node.parent && parent.indexOf(node.parent) == -1) {
            parent.push(node.parent);
          }
        });

        if (parent.length) {
          setHeight(parent, num + 1);
        }
      };

      var appendLeaf = []; //增加的葉子節(jié)點(diǎn)

      if (appendData.children.length) {
        d.children = [];
        appendData.children.forEach(function (item, index) {
          var newNode = d3.hierarchy(item);
          newNode.parent = d;
          newNode.height = -1;
          setDepth(newNode, d.depth + 1, appendLeaf);
          d.children.push(newNode);
        });
      }

      if (appendLeaf.length) {
        setHeight(appendLeaf, 0);
      }

      if (source.data.name == "更多") {
        source.parent.descendants().forEach((d) => {
          d._children = d.children;
          d.id = direction + this.uuid();
        });
      } else {
        source.descendants().forEach((d) => {
          d._children = d.children;
          d.id = direction + this.uuid();
        });
      }
      this.update(d, direction);
    },
    getNode(d, direction, source, type) {
      let mynodes;
      // 注釋ly---start
      // if (!d.data.isNextNode && d.data.type != -1) {
      //   return;
      // }
      // 注釋ly---end
      // console.log("重組數(shù)據(jù)");
      if (d.data.name == "更多") {
        var num = d.data.val / 5;
        if (num <= 5) {
          var arr = d.data.childrend;
        } else {
          var arr = d.data.childrend.slice(0, d.parent.children.length + 4);
          arr[d.parent.children.length + 4] = {
            nodeName: "更多",
            type: -1,
            val: d.data.childrend.length - d.parent.children.length + 4,
            childrend: d.data.childrend,
          };
        }
        var appendData = {
          children: arr,
        };
        this.DataReor(d.parent, direction, source, appendData);
      } else if (type == 1) {
        var appendData = {
          children: [],
        };
        this.DataReor(d, direction, source, appendData);
      } else {
        //請(qǐng)求數(shù)據(jù)
        // console.log(d.data, "請(qǐng)求數(shù)據(jù)參數(shù) update");
        $("#loading,.shaow").show();
        let data = [];
        $("#loading,.shaow").hide();
        if (data.length == 0) {
          html =
            '<div class="tk"><p style="font-size: 14px;color: #359ffb">' +
            d.data.name +
            "</p><span>暫無(wú)數(shù)據(jù)</span></div>";
          $(".shaow").show();
          $("body").append(html);
          return;
        }
        mynodes = data;
        if (data.length > 10) {
          data = data.slice(0, 10);
          data[11] = {
            name: "更多",
            type: -1,
            val: mynodes.length - 10,
            childrend: mynodes.slice(0, mynodes.length),
          };
        }
        var appendData = {
          children: data,
        };
        this.DataReor(d, direction, source, appendData);
      }
    },
    //數(shù)據(jù)處理
    dealData(data) {
      this.direction.forEach((item) => {
        this.root[item] = d3.hierarchy(data[item]);
        this.root[item].x0 = this.centralPoint[0]; //根節(jié)點(diǎn)x坐標(biāo)
        this.root[item].y0 = this.centralPoint[1]; //根節(jié)點(diǎn)Y坐標(biāo)
        this.porData(this.root[item], item);
      });
    },
    //遍歷
    porData(obj, item) {
      obj.descendants().forEach((d) => {
        d._children = d.children;
        d.id = item + this.uuid();
      });
      this.update(obj, item);
    },
    //畫(huà)根節(jié)點(diǎn)
    drawRoot() {
      const title = this.container
        .append("g")
        .attr("id", "rootTitle")
        .attr(
          "transform",
          `translate(${this.centralPoint[1]},${this.centralPoint[0]})`
        );
      title
        .append("svg:rect")
        .attr("class", "rootTitle")
        .attr("y", 0)
        .attr("x", -this.rootNodeLength / 2)
        .attr("width", this.rootNodeLength)
        .attr("height", 0)
        .attr("rx", 5) //圓角
        .style("fill", this.themeColor);
      this.rootName.forEach((name, index) => {
        title
          .append("text")
          .attr("fill", "white")
          .attr("y", function () {
            return 5;
          })
          .attr("text-anchor", "middle")
          .style("font-size", function () {
            if (index == 1) {
              return "10px";
            } else {
              return "15px";
            }
          })
          .text(name);

        let lineHeight = (index + 2) * this.textSpace;
        d3.select("#rootTitle rect")
          .attr("height", lineHeight)
          .attr("y", -lineHeight / 2);
      });
    },
    seat(newArr, dirRight, data) {
      for (var j = 0; j < newArr.length; j++) {
        if (j != 0) {
          for (var i = 0; i < data[j].length; i++) {
            data[j][i].y = data[j - 1][0].y + newArr[j - 1] - 40;
          }
        }
      }
    },
    testLength(data, dirRight) {
      var level = [],
        width1,
        width2;

      for (var i = 0; i < data.length; i++) {
        var newArr = new Array();

        for (var j = 0; j < data[i].length; j++) {
          if (data[i][j].data.attName) {
            svg
              .append("text")
              .style("font-size", this.fontSize)
              .text((d) => "(" + data[i][j].data.attName + ")")
              .attr("class", "test")
              .attr("width", function (d) {
                width3 = d3.select(this.getComputedTextLength())._groups[0][0];
                console.log(width3,"width3");
              });
          }
          if (data[i][j].data.ratio) {
            svg
              .append("text")
              .style("font-size", this.fontSize)
              .text(function (d) {
                const len = data[i][j].data.name.length;
                if (len > 20) {
                  return data[i][j].data.name.substring(0, 20) + "...";
                } else {
                  return data[i][j].data.name;
                }
              })
              .attr("class", "test")
              .attr("width", function (d) {
                width1 = d3.select(this.getComputedTextLength())._groups[0][0];
              });
            svg
              .append("text")
              .style("font-size", this.fontSize)
              .text((d) => data[i][j].data.ratio)
              .attr("class", "test")
              .attr("width", function (d) {
                width2 = d3.select(this.getComputedTextLength())._groups[0][0];
              });

            $(".test").remove();
            if (data[i][j].data.attName) {
              if (Number(width1) + Number(width3) > Number(width2)) {
                newArr.push(Number(width1) + Number(width3) + 100);
              } else {
                newArr.push(Number(width2) + 100);
              }
            } else {
              if (Number(width1) > Number(width2)) {
                newArr.push(Number(width1) + 100);
              } else {
                newArr.push(Number(width2) + 100);
              }
            }
          } else {
            svg
              .append("text")
              .style("font-size", this.fontSize)
              .text(function (d) {
                if (data[i][j].data.name) {
                  const len = data[i][j].data.name.length;
                  if (len > 20) {
                    return data[i][j].data.name.substring(0, 20) + "...";
                  } else {
                    return data[i][j].data.name;
                  }
                }
              })
              .attr("class", "test")
              .attr("width", function (d) {
                width1 = d3.select(this.getComputedTextLength())._groups[0];

                newArr.push(Number(width1) + 100);
                data.width1 = d3.select(
                  this.getComputedTextLength()
                )._groups[0];
              });
            $(".test").remove();
          }
        }
        level.push(Math.max.apply(null, newArr));
      }

      this.seat(level, dirRight, data);
    },

    //開(kāi)始繪圖
    update(source, direction) {
      // console.log(source, direction, "update");
      let that = this;
      dirRight = direction === "r" ? 1 : -1; //方向?yàn)橛?左
      forUpward = direction == "r";
      let className = `${direction}gNode`;
      let tree = this.treeMap(this.root[direction]);
      let nodes = tree.descendants();
      let links = tree.links();
      var data = [];
      if (nodes.length > 1) {
        for (var i = 0; i < nodes.length; i++) {
          if (!data[nodes[i].depth]) {
            var arr = [];
            arr.push(nodes[i]);
            data[nodes[i].depth] = arr;
          } else {
            data[nodes[i].depth].push(nodes[i]);
          }
        }
        //檢測(cè)最大長(zhǎng)度
        this.testLength(data, dirRight);
      }
      nodes.forEach((d) => {
        d.y = dirRight * (d.y + this.rootNodeLength / 2) + this.centralPoint[1];
        d.x = d.x + this.centralPoint[0];
      });

      //根據(jù)class名稱(chēng)獲取左或者右的g節(jié)點(diǎn),達(dá)到分塊更新
      const node = this.container
        .selectAll(`g.${className}`)
        .data(nodes, (d) => d.id);
      //新增節(jié)點(diǎn)焕蹄,tree會(huì)根據(jù)數(shù)據(jù)內(nèi)的children擴(kuò)展相關(guān)節(jié)點(diǎn)
      const nodeEnter = node
        .enter()
        .append("g")
        .attr("id", (d) => `g${d.id}`)
        .attr("class", className)
        .attr("transform", (d) => `translate(${source.y0},${source.x0})`)
        .attr("fill-opacity", 0)
        .attr("stroke-opacity", 0)
        .on("click", function (d) {
          d3.select(this)
            .selectAll(".node-circle .node-circle-vertical")
            .transition()
            .duration(that.duration)
            .attr("stroke-width", function (d) {
              if (d.children) {
                return 1;
              } else {
                return 0;
              }
            });

          if (d.data.name == "更多") {
            return that.clickNode(d, direction, source);
          } else if (d.depth !== 0) {
            return that.clickNode(d, direction, source);
          }
        });
      nodeEnter.each((d) => {
        if (d.depth > 0) {
          this.drawText(`g${d.id}`, dirRight);
          if (d.data.attName) {
            this.drawCodeText(`g${d.id}`, dirRight);
          }
          if (d.data.ratio) {
            this.drawTsText(`g${d.id}`, dirRight);
          }

          this.drawRect(`g${d.id}`, dirRight);
          this.marker(`g${d.id}`, dirRight);
        }

        if (d.depth > 0) {
          const width = Math.min(d.data.name.length * 14, this.rectMinWidth);
          let right = dirRight > 0;
          let xDistance = right ? width : -width;
          this.drawCircle(`g${d.id}`, dirRight, source, direction);
          this.drawSign(`g${d.id}`, dirRight); //畫(huà)標(biāo)記
        }

        //畫(huà)節(jié)點(diǎn)數(shù)量
        // console.log(d, "畫(huà)節(jié)點(diǎn)數(shù)量");
        if (d.data && d.data.type == -1) {
          this.drawLength(`g${d.id}`, dirRight);
        }
      });

      // 更新節(jié)點(diǎn):節(jié)點(diǎn)enter和exit時(shí)都會(huì)觸發(fā)tree更新
      const nodeUpdate = node
        .merge(nodeEnter)
        .transition()
        .duration(this.duration)
        .attr("transform", function (d) {
          if (d.data && d.data.isNextNode) {
            d3.select(this)
              .selectAll(".node-circle .node-circle-vertical")
              .transition()
              .duration(this.duration)
              .attr("stroke-width", function (d) {
                if (d.children) {
                  return 0;
                } else {
                  return 1;
                }
              });
          }

          var index = 0;

          return "translate(" + d.y + "," + d.x + ")";
        })
        .attr("fill-opacity", 1)
        .attr("stroke-opacity", 1);

      // 移除節(jié)點(diǎn):tree移除掉數(shù)據(jù)內(nèi)不包含的節(jié)點(diǎn)(即逾雄,children = false)
      const nodeExit = node
        .exit()
        .transition()
        .duration(this.duration)
        .remove()
        .attr("transform", (d) => `translate(${source.y},${source.x})`)
        .attr("fill-opacity", 0)
        .attr("stroke-opacity", 0);

      // Update the links 根據(jù) className來(lái)實(shí)現(xiàn)分塊更新
      const link = this.container
        .selectAll(`path.${className}`)
        .data(links, (d) => d.target.id);

      // Enter any new links at the parent's previous position.
      //insert是在g標(biāo)簽前面插入,防止連接線擋住G節(jié)點(diǎn)內(nèi)容
      const linkEnter = link
        .enter()
        .insert("path", "g")
        .attr("class", className)
        .attr("d", (d) => {
          const o = { x: source.x0, y: source.y0 };

          return this.diagonal({ source: o, target: o });
        })
        .attr("fill", "none")
        .attr("stroke-width", 0.5)
        .attr("stroke", "#D8D8D8");

      // Transition links to their new position.
      link
        .merge(linkEnter)
        .transition()
        .duration(this.duration)
        .attr("d", this.diagonal);

      // Transition exiting nodes to the parent's new position.
      link
        .exit()
        .transition()
        .duration(this.duration)
        .remove()
        .attr("d", (d) => {
          const o = { x: source.x, y: source.y };

          return this.diagonal({ source: o, target: o });
        });

      // Stash the old positions for transition.
      this.root[direction].eachBefore((d) => {
        d.x0 = d.x;
        d.y0 = d.y;
      });
    },
    //畫(huà)連接線
    diagonal({ source, target }) {
      let s = source,
        d = target;

      if (forUpward) {
        return (
          "M" +
          s.y +
          "," +
          s.x +
          "L" +
          (s.y + (d.y - s.y) - 20) +
          "," +
          s.x +
          "L" +
          (s.y + (d.y - s.y) - 20) +
          "," +
          d.x +
          "L" +
          d.y +
          "," +
          d.x
        );
      } else {
        return (
          "M" +
          s.y +
          "," +
          s.x +
          "L" +
          (s.y + (d.y - s.y) + 20) +
          "," +
          s.x +
          "L" +
          (s.y + (d.y - s.y) + 20) +
          "," +
          d.x +
          "L" +
          d.y +
          "," +
          d.x
        );
      }
    },

    //畫(huà)箭頭
    marker(id, dirRight) {
      let gMark = d3
        .select(`#${id}`)
        .append("g")
        .attr(
          "transform",
          dirRight > 0 ? "translate(-20,0)" : "translate(12,0)"
        );
      return gMark
        .insert("path", "text")

        .attr("d", function (d) {
          if (d.data.nodeType == 0 && dirRight > 0) {
            return "M0,0L0,3L9,0L0,-3Z";
          } else if (d.data.nodeType == 0 && dirRight < 0) {
            return "M0,0L9,-3L9,3Z";
          }
        })
        .style("fill", (d) => this.getRectStorke(d.data.name));
    },

    //華標(biāo)記
    drawSign(id, dirRight) {
      return d3
        .select(`#${id}`)
        .insert("circle", "text")
        .attr("cx", dirRight > 0 ? -5 : 5)
        .attr("y", -2.5)
        .attr("r", function (d) {
          if (d.data.nodeType == 0) {
            return 4;
          }
        })
        .style("fill", (d) => this.getRectStorke(d.data.name));
    },
    //畫(huà)文本
    drawText(id, dirRight) {
      let that = this;
      dirRight = dirRight > 0; //右為1腻脏,左為-1
      return d3
        .select(`#${id}`)
        .append("text")
        .attr("y", function (d) {
          if (d.data.ratio) {
            return that.textPadding - 8;
          } else {
            return that.textPadding;
          }
        })
        .attr("x", (d) => (dirRight ? that.textPadding : -that.textPadding))
        .attr("text-anchor", dirRight ? "start" : "end")
        .style("font-size", that.fontSize)
        .text(function (d) {
          const len = d.data.name.length;
          if (len > 20) {
            return d.data.name.substring(0, 20) + "...";
          } else {
            return d.data.name;
          }
        })
        .attr("fill", function (d) {
          if (d.data.nodeType == -1) {
            return "rgb(33, 150, 243)";
          } else if (d.data.nodeType == 0 || d.data.attName) {
            return "#000";
          } else {
            return "#333";
          }
        })

        .attr("width", function (d) {
          circlewidth1 = d3.select(this.getComputedTextLength())._groups[0][0];
          return d3.select(this.getComputedTextLength())._groups[0][0];
        });
    },
    //畫(huà)子文本
    drawTsText(id, dirRight) {

      let that = this;
      return d3
        .select(`#${id} text`)
        .append("tspan")
        .attr("fill", (d) => (d.data.attName ? "#fff" : "#999"))
        .attr("y", function (d) {
          if (d.data.ratio) {
            return that.textPadding + 8;
          }
        })
        .attr("x", function (d) {
          if (dirRight > 0) {
            return that.textPadding;
          } else {
            return -5;
          }
        })
        .style("font-size", "11px")
        .text(function (d) {
          return d.data.ratio;
        })

        .attr("width", function (d) {
          circlewidth2 = d3.select(this.getComputedTextLength())._groups[0][0];
          return d3.select(this.getComputedTextLength())._groups[0][0];
        });
    },
    //畫(huà)股票代碼
    drawCodeText(id, dirRight) {

      return d3
        .select(`#${id} text`)
        .append("tspan")
        .attr("fill", (d) => "#fff")
        .attr("y", function (d) {
          if (d.data.ratio) {
            return this.textPadding + 8;
          }
        })
        .style("font-size", "11px")
        .attr("x", function (d) {
          if (dirRight > 0) {
            return this.textPadding;
          } else {
            return -5;
          }
        })
        .text(function (d) {
          return d.data.attName + " ";
        })

        .attr("width", function (d) {
          circlewidth3 = d3.select(this.getComputedTextLength())._groups[0][0];
          return d3.select(this.getComputedTextLength())._groups[0][0];
        });
    },
    //節(jié)點(diǎn)數(shù)量
    drawLength(id) {

      return d3
        .select(`#${id} text`)
        .append("tspan")
        .attr("fill", (d) => "#999")
        .text(function (d) {
          if (d.data.type == -1) {
            return " (" + d.data.val + ")";
          }
          return " [" + d.data.ratio + "]";
        })
        .attr("fill", "rgb(33, 150, 243)")
        .attr("width", function (d) {
          return d3.select(this.getComputedTextLength())._groups[0];
        });
    },
    //畫(huà)方框陰影
    drawFilter(id) {

      return d3
        .select(`#${id}`)
        .insert("defs", "rect")
        .append("filter")
        .attr("id", `f${id}`)
        .attr("x", 0)
        .attr("y", 0)
        .append("feGaussianBlur")
        .attr("in", "SourceGraphic")
        .attr("stdDeviation", "5");
    },

    //畫(huà)方框
    drawRect(id, dirRight) {

      let that = this;
      let realw = document.getElementById(id).getBBox().width + 25; //獲取g實(shí)際寬度后鸦泳,設(shè)置rect寬度
      if (document.getElementById(id).getBBox().width > 400) {
        realw = 400;
      }

      return d3
        .select(`#${id}`)
        .insert("rect", "text")
        .attr("x", function (d) {
          if (dirRight < 0) {
            return -realw;
          } else {
            0;
          }
        })
        .attr("y", function (d) {
          if (d.data.ratio) {
            return -that.textSpace + that.textPadding - 11;
          } else {
            return -that.textSpace + that.textPadding - 4;
          }
        })
        .attr("width", function (d) {
          // if (d.data.isNextNode) {//判斷是否有下級(jí)節(jié)點(diǎn)
          //     return realw
          // } else {
          //     return realw - 15
          // }
          return realw;
        })
        .attr("height", function (d) {
          if (d.data.ratio) {
            return that.textSpace + that.textPadding + 22;
          } else {
            return that.textSpace + that.textPadding + 8;
          }
        })
        .attr("stroke-width", (d) =>
          d.data.nodeType == 0 || d.data.type == -1 || d.data.attName ? 0 : 0.5
        )
        .attr("rx", 2) //圓角

        .style("stroke", (d) =>
          d.data.nodeType == -1 ? "" : that.getRectStorke(d.parent.data.name)
        )
        .style("fill", function (d) {
          if (d.data.nodeType == -1) {
            return "rgb(241, 241, 241)";
          } else if (d.data.nodeType == 0) {
            return that.getRectStorke(d.data.name);
          } else if (d.data.attName) {
            return "rgb(33, 150, 243)";
          } else {
            return "#fff";
          }
        });
    },

    //畫(huà)circle
    drawCircle(id, dirRight, source, direction) {

      let gMark = d3
        .select(`#${id}`)
        .append("g")
        .attr("class", "node-circle")
        .attr("stroke", "rgb(153, 153, 153)")
        .attr("transform", function (d) {
          if (d.data.ratio) {
            if (circlewidth1 > circlewidth2) {
              leng = Number(circlewidth1) + 15;
            } else {
              leng = Number(circlewidth2) + 15;
            }

            //leng = Number(circlewidth1) + Number(circlewidth2) + 25;
            if (leng > 390) {
              leng = 390;
            }
            if (dirRight == 1) {
              return "translate(" + leng + ",0)";
            } else {
              return "translate(" + -leng + ",0)";
            }
          } else {
            leng = Number(circlewidth1) + 15;
            if (dirRight == 1) {
              return "translate(" + leng + ",0)";
            } else {
              return "translate(" + -leng + ",0)";
            }
          }
        })
        // .attr('stroke-width', d => d.data.isNextNode ? 1 : 0);   判斷是否有下級(jí)節(jié)點(diǎn)
        .attr("stroke-width", (d) => (d.data.type == "-1" ? 0 : 1));

      gMark
        .append("circle")
        .attr("fill", "none")
        .attr("r", function (d) {
          if (d.data.type == "-1") {
            return 0;
          }
          return 5;
        }) //根節(jié)點(diǎn)不設(shè)置圓圈
        .attr("fill", "#ffffff");
      let padding = this.circleR - 2;

      gMark.append("path").attr("d", `m -${padding} 0 l ${2 * padding} 0`); //橫線

      gMark
        .append("path") //豎線,根據(jù)展開(kāi)/收縮動(dòng)態(tài)控制顯示
        .attr("d", `m 0 -${padding} l 0 ${2 * padding}`)
        .attr("stroke-width", function (d) {
          if (d.data.type == "-1") {
            return 0;
          }
          return 1;
        })
        .attr("class", "node-circle-vertical");
      return gMark;
    },

    //點(diǎn)擊某個(gè)節(jié)點(diǎn)
    // clickNode(d, direction, source) {
    //   if (d.children) {
    //     d._children = d.children;
    //     d.children = null;
    //     this.getNode(d, direction, source, 1);
    //   } else {
    //     d.children = d._children;
    //     d._children = [];
    //     this.getNode(d, direction, source); //原注
    //   }
    //   if (d.data.name == "更多") {
    //     this.getNode(d, direction, source);
    //     return;
    //   }
    //   this.update(d, direction); //原注
    // },

    expand(d, direction, source) {

      if (d.data.name == "更多") {
        this.getNode(d, direction, source);
        return;
      }
      if (d._children) {
        d.children = d._children;

        d._children = null;
        this.update(d, direction);
      }
    },

    collapse(d, direction, obj) {

      if (d.children) {
        d._children = d.children;
        d.children = null;
        console.log(d._children, "_children");
      }
      if (obj == 1) {
        this.update(d, direction);
      }
    },

    //點(diǎn)擊某個(gè)節(jié)點(diǎn)ly
     clickNode(d, direction, source) {
       if (d.children || d.children) {
         this.collapse(d, direction, 1);
       } else {
         this.expand(d, direction, source);
       }
     },
    getTsTextColor(name) {

      switch (name) {
        case "股東":
          return "darkgray";
        case "供應(yīng)商":
          return "#FF9800";
        case "合伙人":
          return "green";
        default:
          return "black";
      }
    },
    //末 節(jié)點(diǎn) 邊框顏色
    getRectStorke(name) {
      switch (name) {
        case "分支機(jī)構(gòu)":
          return "rgb(255, 234, 218)";
        case "對(duì)外投資":
          return "rgb(215, 236, 255)";
        case "股東":
          return "rgb(211, 234, 241)";
        case "高管":
          return "rgb(237, 227, 244)";
        default:
          return "rgb(133, 165, 255)";
      }
    },
    isNull(val) {
      return val ? val : "";
    },
  },
};
</script>

style

.tree04 {
  background: #fff;
  touch-action: none;
  padding: 0;
  margin: 0;
  height: 100%;
  max-width: 100%;
  overflow: hidden;
  font-family: "PingFangSC-Regular", "PingFangSC-Light", "PingFang SC",
    sans-serif, "Microsoft YaHei";
}

總結(jié)

本次開(kāi)發(fā)使得對(duì)于d3有一個(gè)進(jìn)一步的了解永品,雖然主要復(fù)雜的功能是參考別人的做鹰,但是通過(guò)此次使用過(guò)后對(duì)于一些細(xì)枝末節(jié)的地方還是可以持續(xù)更新優(yōu)化!
寫(xiě)這個(gè)文檔主要是為了記錄一下過(guò)程鼎姐,并且日后也方便查看钾麸。
持續(xù)更新更振。。喂走。

參考網(wǎng)站

svg
d3中文網(wǎng)站

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載殃饿,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。
  • 序言:七十年代末芋肠,一起剝皮案震驚了整個(gè)濱河市乎芳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帖池,老刑警劉巖奈惑,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異睡汹,居然都是意外死亡肴甸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)囚巴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)原在,“玉大人,你說(shuō)我怎么就攤上這事彤叉∈粒” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵秽浇,是天一觀的道長(zhǎng)浮庐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)柬焕,這世上最難降的妖魔是什么审残? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮斑举,結(jié)果婚禮上搅轿,老公的妹妹穿的比我還像新娘。我一直安慰自己懂昂,他們只是感情好介时,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著凌彬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪循衰。 梳的紋絲不亂的頭發(fā)上铲敛,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音会钝,去河邊找鬼伐蒋。 笑死工三,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的先鱼。 我是一名探鬼主播俭正,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼焙畔!你這毒婦竟也來(lái)了掸读?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤宏多,失蹤者是張志新(化名)和其女友劉穎儿惫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體伸但,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肾请,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了更胖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铛铁。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖却妨,靈堂內(nèi)的尸體忽然破棺而出饵逐,到底是詐尸還是另有隱情,我是刑警寧澤管呵,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布梳毙,位于F島的核電站,受9級(jí)特大地震影響捐下,放射性物質(zhì)發(fā)生泄漏账锹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一坷襟、第九天 我趴在偏房一處隱蔽的房頂上張望奸柬。 院中可真熱鬧,春花似錦婴程、人聲如沸廓奕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桌粉。三九已至,卻和暖如春衙四,著一層夾襖步出監(jiān)牢的瞬間铃肯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工传蹈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留押逼,地道東北人步藕。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像挑格,于是被迫代替她去往敵國(guó)和親咙冗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容