自学内容网 自学内容网

nuxt、vue树形图d3.js

在这里插入图片描述

直接上代码

//安装
npm i d3 --save
<template>
  <div class="d3">
    <div :id="id" class="d3-content"></div>
  </div>
</template>
<script>
import * as d3 from "d3";

export default {
  props: {
    data: Object,
    nodeWidth: {
      type: Number,
      default: 340,
    },
    nodeHeight: {
      type: Number,
      default: 40,
    },
    active: {
      type: String,
      default: "",
    },
  },
  data() {
    return {
      id: "TreeMap" + randomString(4),
      deep: 0,
      treeData: null,
      show: true,
      demoData: {
        label: "中国",
        // link: "demo",
        url: "https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD/1122445?fr=aladdin",
        children: [
          {
            label: "浙江45468761321",
            // link: "isClick",
            disabled: true,
            children: [
              { label: "杭州999999999" },
              { label: "宁波" },
              { label: "温州" },
              { label: "绍兴" },
            ],
          },
          {
            label: "广西",
            children: [
              {
                label: "桂林56465465465464",
                children: [
                  { label: "秀峰区" },
                  { label: "叠彩区" },
                  { label: "象山区" },
                  { label: "七星区" },
                ],
              },
              { label: "南宁" },
              { label: "柳州" },
              { label: "防城港" },
            ],
          },
        ],
      },
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.drawMap();
      window.handleCustom=this.handleCustom;

    });
  },
  methods: {
    drawMap() {
      let that = this;
      // 源数据
      let data = {};
      // 判断data是否为空对象
      if (this.data && JSON.stringify(this.data) !== "{}") {
        data = this.data;
      } else {
        data = this.demoData;
      }
      if (!this.treeData) {
        this.treeData = data;
      } else {
        // 清空画布
        d3.select("#" + this.id)
          .selectAll("svg")
          .remove();
      }
      let leafList = [];
      getTreeLeaf(data, leafList);
      let leafNum = leafList.length;
      let TreeDeep = getDepth(data);
      // 左右内边距
      let mapPaddingLR = 10;
      // 上下内边距
      let mapPaddingTB = 0;
      let mapWidth = this.nodeWidth * TreeDeep + mapPaddingLR * 2;
      let mapHeight = (this.nodeHeight - 4) * leafNum + mapPaddingTB * 2;
      // 定义画布—— 外边距 10px
      let svgMap = d3
        .select("#" + this.id)
        .append("svg")
        .attr("width", mapWidth)
        .attr("height", mapHeight)
        .style("margin", "0px");
      // 定义树状图画布
      let treeMap = svgMap
        .append("g")
        .attr(
          "transform",
          "translate(" +
            mapPaddingLR +
            "," +
            (mapHeight / 2 - mapPaddingTB) +
            ")"
        );
      // 将源数据转换为可以生成树状图的数据(有节点 nodes 和连线 links )
      let treeData = d3
        .tree()
        // 设置每个节点的尺寸
        .nodeSize(
          // 节点包含后方的连接线 [节点高度,节点宽度]
          [this.nodeHeight, this.nodeWidth]
        )
        // 设置树状图节点之间的垂直间隔
        .separation(function (a, b) {
          // 样式一:节点间等间距
          // return (a.parent == b.parent ? 1: 2) / a.depth;
          // 样式二:根据节点子节点的数量,动态调整节点间的间距
          let rate =
            (a.parent == b.parent
              ? b.children
                ? b.children.length / 2
                : 1
              : 2) / a.depth;
          // 间距比例不能小于0.7,避免间距太小而重叠
          if (rate < 0.7) {
            rate = 0.7;
          }
          return rate;
        })(
        // 创建层级布局,对源数据进行数据转换
        d3.hierarchy(data).sum(function (node) {
          // 函数执行的次数,为树节点的总数,node为每个节点
          return node.value;
        })
      );
      // 贝塞尔曲线生成器
      let Bézier_curve_generator = d3
        .linkHorizontal()
        .x(function (d) {
          return d.y;
        })
        .y(function (d) {
          return d.x;
        });
      //绘制边
      treeMap
        .selectAll("path")
        // 节点的关系 links
        .data(treeData.links())
        .enter()
        .append("path")
        .attr("d", function (d) {
          // 根据name值的长度调整连线的起点
          var start = {
            x: d.source.x,
            // 连线起点的x坐标
            // 第1个10为与红圆圈的间距,第2个10为link内文字与边框的间距,第3个10为标签文字与连线起点的间距,60为自定义html
            y:
              d.source.y +
              10 +
              (d.source.data.link ? getPXwidth(d.source.data.link) + 10 : 0) +
              getPXwidth(d.source.data.label) +
              (!d.source.data.children?82:0) +
              20,
          };
          var end = { x: d.target.x, y: d.target.y };
          return Bézier_curve_generator({ source: start, target: end });
        })
        .attr("fill", "none")
        .attr("stroke", "#00AB6B")
        // 虚线
        // .attr("stroke-dasharray", "8")
        .attr("stroke-width", 1);

      // 创建分组——节点+文字
      let groups = treeMap
        .selectAll("g")
        // 节点 nodes
        .data(treeData.descendants())
        .enter()
        .append("g")
        .attr("transform", function (d) {
          var cx = d.x;
          var cy = d.y;
          return "translate(" + cy + "," + cx + ")";
        });

      //绘制节点(节点前的圆圈)
      groups
        .append("circle")
        // 树的展开折叠
        .on("click", function (event, node) {
          let data = node.data;
          if (data.children) {
            data.childrenTemp = data.children;
            data.children = null;
          } else {
            data.children = data.childrenTemp;
            data.childrenTemp = null;
          }
          that.drawMap();
        })
        .attr("cursor", "pointer")
        .attr("r", 4)
        .attr("fill", function (d) {
          if (d.data.childrenTemp) {
            return "#00AB6B";
          } else {
            return "white";
          }
        })
        .attr("stroke", "#00AB6B")
        .attr("stroke-width", 1);
      //绘制标注(节点前的矩形)
      groups
        .append("rect")
        .attr("x", 8)
        .attr("y", -10)
        .attr("width", function (d) {
          return d.data.link ? getPXwidth(d.data.link) + 10 : 0;
        })
        .attr("height", 22)
        .attr("fill", "red")
        .attr("border", "blue")
        // 添加圆角
        .attr("rx", 4);
      //绘制链接方式
      groups
        .append("text")
        .attr("x", 12)
        .attr("y", -5)
        .attr("dy", 10)
        .attr("fill", "white")
        .attr("font-size", 12)
        .text(function (d) {
          return d.data.link;
        });
      //绘制文字
      groups
        .append("text")
        .on("click", function (event, node) {
          let data = node.data;
          // 被禁用的节点,点击无效
          if (data.disabled) {
            return;
          }
          // 有外链的节点,打开新窗口后恢复到思维导图页面
          if (data.url) {
            window.open(data.url);
            that.$emit("activeChange", "map");
            return;
          }
          // 标准节点—— 传出 prop
          if (data.dicType) {
            that.$emit("dicTypeChange", data.dicType);
          }
          // 标准节点—— 传出 prop
          if (data.prop) {
            that.$emit("activeChange", data.prop);
          }
        })
        .attr("x", function (d) {
          return 12 + (d.data.link ? getPXwidth(d.data.link) + 10 : 0);
        })
        .attr("fill", function (d) {
          if (d.data.prop === that.active) {
            return "#409EFF";
          }
        })
        .attr("font-weight", function (d) {
          if (d.data.prop === that.active) {
            return "bold";
          }
        })
        .attr("font-size", 14)
        .attr("cursor", function (d) {
          if (d.data.disabled) {
            return "not-allowed";
          } else {
            return "pointer";
          }
        })
        .attr("y", -5)
        .attr("dy", 10)
        .attr("slot", function (d) {
          return d.data.prop;
        });
      // .text(function (d) {
      //   return d.data.label;
      // });

      groups
        .append("foreignObject")
        .attr("width", (d) => {
          return getPXwidth(d.data.label) + 22 + (!d.data.children?82:0);
        })
        .attr("height", 100)
        .attr("x", function (d) {
          return 12 + (d.data.link ? getPXwidth(d.data.link) + 10 : 0);
        })
        .on("click", function (event, node) {
          
        })
        .attr("y", -10)
        .append("xhtml:div")
        .style("font", '14px "Helvetica Neue"')
        .html((d) => {
          let _html = `
            <div class="custom-html">
              <div>${d.data.label}</div>
            </div>`;
          if(!d.data.children){
            _html = `
            <div class="custom-html">
              <div>${d.data.label}</div>
              <div οnclick="handleCustom(${1})"><i class="iconfont">&#xe648;</i>视频课</div>
            </div>`;
          }
          
          return _html
        });
    },
    handleCustom(data){
      debugger
    }
  },
};

// 获取树的深度
function getDepth(json) {
  var arr = [];
  arr.push(json);
  var depth = 0;
  while (arr.length > 0) {
    var temp = [];
    for (var i = 0; i < arr.length; i++) {
      temp.push(arr[i]);
    }
    arr = [];
    for (var i = 0; i < temp.length; i++) {
      if (temp[i].children && temp[i].children.length > 0) {
        for (var j = 0; j < temp[i].children.length; j++) {
          arr.push(temp[i].children[j]);
        }
      }
    }
    if (arr.length >= 0) {
      depth++;
    }
  }
  return depth;
}

// 提取树的子节点,最终所有树的子节点都会存入传入的leafList数组中
function getTreeLeaf(treeData, leafList) {
  // 判断是否为数组
  if (Array.isArray(treeData)) {
    treeData.forEach((item) => {
      if (item.children && item.children.length > 0) {
        getTreeLeaf(item.children, leafList);
      } else {
        leafList.push(item);
      }
    });
  } else {
    if (treeData.children && treeData.children.length > 0) {
      getTreeLeaf(treeData.children, leafList);
    } else {
      leafList.push(treeData);
    }
  }
}

// 获取包含汉字的字符串的长度
function getStringSizeLength(string) {
  //先把中文替换成两个字节的英文,再计算长度
  return string.replace(/[\u0391-\uFFE5]/g, "aa").length;
}

// 生成随机的字符串
function randomString(strLength) {
  strLength = strLength || 32;
  let strLib = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz";
  let n = "";
  for (let i = 0; i < strLength; i++) {
    n += strLib.charAt(Math.floor(Math.random() * strLib.length));
  }
  return n;
}

// 获取字符串的像素宽度
function getPXwidth(str, fontSize = "12px", fontFamily = "Microsoft YaHei") {
  var span = document.createElement("span");
  var result = {};
  result.width = span.offsetWidth;
  result.height = span.offsetHeight;
  span.style.visibility = "hidden";
  span.style.fontSize = fontSize;
  span.style.fontFamily = fontFamily;
  span.style.display = "inline-block";
  document.body.appendChild(span);
  if (typeof span.textContent != "undefined") {
    span.textContent = str;
  } else {
    span.innerText = str;
  }
  result.width = parseFloat(window.getComputedStyle(span).width) - result.width;
  // 字符串的显示高度
  // result.height = parseFloat(window.getComputedStyle(span).height) - result.height;
  return result.width;
}
</script>
<style lang="scss" scoped>
.d3 {
  position: relative;
  overflow: hidden;
  width: calc(100%);
  min-height: 500px;
  overflow-x: scroll;
  .d3-content {
    position: absolute;
    width: max-content;
    ::v-deep .custom-html {
      display: flex;
      div {
        i {
          font-size: 12px;
          margin-right: 4px;
        }
        &:nth-child(2) {
          margin-left: 10px;
          background: #f2faf7;
          border: 0.5px solid #c3e7da;
          border-radius: 4px;
          color: #00ab6b;
          font-size: 12px;
          padding: 0 4px;
          height: 20px;
          cursor: pointer;
        }
      }
    }
  }
}
</style>


原文地址:https://blog.csdn.net/weixin_43728978/article/details/140213648

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!