vue、nuxt使用D3,mindMap自定义html

2 篇文章 0 订阅
1 篇文章 0 订阅

在这里插入图片描述

安装D3

npm install d3 --save

首先确认你使用的是vue还是Nuxt

vue

//如果使用的是vue就把代码中的注释解开,再把this.$d3替换成d3
// import * as d3 from "d3";

Nuxt

在这里插入图片描述
在这里插入图片描述

创建组件mindMap.vue

<template>
  <div class="catalogue">
    <div class="header">
      <p>{{ objs.course_name }}</p>
      <p>{{ objs.course_name_en }}</p>
    </div>
    <div class="d3" :style="{ height: `${d3Height + 48}px` }">
      <div :id="id" class="d3-content"></div>
    </div>
  </div>
</template>

<script>
// import * as d3 from "d3";

export default {
  props: {
    objs: Object,
    data: Object,
    nodeWidth: {
      type: Number,
      default: 340,
    },
    nodeHeight: {
      type: Number,
      default: 40,
    },
    active: {
      type: String,
      default: "",
    },
  },
  components: {},
  data() {
    return {
      isClient: typeof window !== "undefined",

      canvasWidth: 1032,
      id: "TreeMap" + randomString(4),
      deep: 0,
      treeData: null,
      show: true,

      initWidth: 0,
      initHeight: 500,
      isPlayBtn: false,
      isPlayId: "",
      d3Height: 500,
      isWidth: 0,
    };
  },
  mounted() {
    window.addEventListener("resize", () => {
      let el = document.querySelector(".catalogue");
      this.canvasWidth = el.offsetWidth;
      // if (process.browser) {
      //   this.$nextTick(() => {
      //     this.drawMap();
      //     window.handleCustom = this.handleCustom;
      //   });
      // }
    });
    if (process.browser) {
      this.$nextTick(() => {
        let el = document.querySelector(".catalogue");
        this.canvasWidth = el.offsetWidth;
        this.drawMap();
        window.handleCustom = this.handleCustom;
      });
    }
  },
  methods: {
    drawMap(boo) {
      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 {
        // 清空画布
        this.$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;
      // 上下内边距
      if (!boo) {
        this.initWidth = this.nodeWidth * TreeDeep + mapPaddingLR * 2;
        this.initHeight = (this.nodeHeight - 4) * leafNum + mapPaddingTB * 2;
      }
      let mapWidth = boo
        ? this.initWidth
        : this.nodeWidth * TreeDeep + mapPaddingLR * 2;
      // let mapWidth = this.canvasWidth;
      let mapHeight = (this.nodeHeight - 4) * leafNum + mapPaddingTB * 2;
      this.d3Height = mapHeight < 500 ? 500 : mapHeight;
      // 定义画布—— 外边距 10px
      let svgMap = this.$d3
        .select("#" + this.id)
        .append("svg")
        .attr("width", mapWidth + 100)
        .attr("height", mapHeight < 500 ? 500 : mapHeight)
        .style("margin", "0px");

      // Zoom functionality
      const zoom = this.$d3
        .zoom()
        .scaleExtent([0.1, 3])
        .on("zoom", (event) => {
          // svgMap.attr('transform', event.transform);
          const transform = event.transform;
          const [x, y] = [transform.x, transform.y];
          const scale = transform.k;
          // Calculate the new transform to keep the tree centered
          const centerX = (mapWidth / 2) * scale;
          const centerY = (mapHeight / 2) * scale;
          const newX = centerX - mapWidth / 2;
          const newY = centerY - mapHeight / 2;

          svgMap.attr(
            "transform",
            `translate(${newX},${newY}) scale(${scale})`
          );
        });

      this.$d3
        .select("#" + this.id)
        .select("svg")
        .call(zoom);
      // 定义树状图画布
      let treeMap = svgMap
        .append("g")
        .attr(
          "transform",
          "translate(" +
            mapPaddingLR +
            "," +
            (mapHeight / 2 - mapPaddingTB + 50) +
            ")"
        );
      // 将源数据转换为可以生成树状图的数据(有节点 nodes 和连线 links )
      let treeData = this.$d3
        .tree()
        // 设置每个节点的尺寸
        .nodeSize(
          // 节点包含后方的连接线 [节点高度,节点宽度]
          [this.nodeHeight, this.nodeWidth]
        )
        // 设置树状图节点之间的垂直间隔
        .separation(function (a, b) {
          // debugger
          // 样式一:节点间等间距
          // 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;
        })(
        // 创建层级布局,对源数据进行数据转换
        this.$d3.hierarchy(data).sum(function (node) {
          // 函数执行的次数,为树节点的总数,node为每个节点
          return node.value;
        })
      );
      // 贝塞尔曲线生成器
      let Bézier_curve_generator = this.$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.title ? getPXwidth(d.source.data.title) + 10 : 0) +
              getPXwidth(d.source.data.label) +
              (d.source.data.detail_type == 2 ? 82 : 0),
          };
          // if (d.source.data.detail_type == 2) {
          //   that.isWidth = getPXwidth(d.source.data.title) + 200;
          // }

          // var end = { x: d.target.x, y: d.target.y };
          var end = { x: d.target.x, y: d.target.y + that.isWidth };

          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;
          // if (d.depth == 3) {
          //   cy = cy + 200;
          // }
          return "translate(" + cy + "," + cx + ")";
        });

      //绘制节点(节点前的圆圈)
      groups
        .append("circle")
        // 树的展开折叠
        .on("click", function (event, node) {
          let data = node.data;
          if (data.children) {
            let isChildren = that.findByID(that.data.children, data.id);
            if (isChildren.children) {
              that.isPlayId = data.id;
            }
            data.childrenTemp = data.children;
            data.children = null;
          } else {
            that.isPlayId = "";
            data.children = data.childrenTemp;
            data.childrenTemp = null;
          }

          that.drawMap(true);
        })
        .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", 14)
        .text(function (d) {
          return d.data.link;
        });


      groups
        .append("foreignObject")
        .attr("width", (d) => {
          return (
            getPXwidth(d.data.title) + 12 + (d.data.detail_type == 2 ? 82 : 0)
          );
        })
        .attr("height", 20)
        .attr("x", function (d) {
          return 12 + (d.data.link ? getPXwidth(d.data.link) + 10 : 0);
        })
        .on("click", function (event, node) {
          if (that.isPlayBtn) {
            document.querySelector(".main-content").scrollTop = 0;
            that.$store.dispatch("user/setCourseVideoId", node.data.id);
          }
        })
        .attr("y", -10)
        .append("xhtml:div")
        .style("font", '14px "Helvetica Neue"', "line-height", "20px")
        .attr("dy", ".35em")
        .style("text-anchor", function(d) { return d.children ? "end" : "start"; })
        .html((d) => {
          let _html = `
            <div class="custom-html">
              <div>${d.data.title?.length > 20 ? d.data.title.slice(0, 20) + '...' : d.data.title}</div>
            </div>`;
          if (d.data.detail_type == 2 && that.isPlayId != d.data.id) {
            _html = `
            <div class="custom-html">
              <div>${d.data.title?.length > 20 ? d.data.title.slice(0, 20) + '...' : d.data.title}</div>
              <div class="custom-html-btn" οnclick="handleCustom(${1})"><i class="iconfont">&#xe648;</i>视频课</div>
            </div>`;
          }

          return _html;
        });
        var tooltip = that.$d3.select("body").append("div")
            .attr("class", "tooltip")
            .style("opacity", 0)
            .style("position", "absolute")

        groups.on("mouseover", function(event, d) {
          if(d.data.title?.length > 20){
            tooltip.transition()
                .duration(200)
                .style("opacity", 1);
            tooltip.html(d.data.title)
                .style("background", "#fff")
                .style("padding", "4px 8px")
                .style("border", "1px solid #f0f0f0")
                .style("left", (event.pageX) + "px")
                .style("top", (event.pageY - 28) + "px");
          }
        })
        .on("mouseout", function(d) {
            tooltip.transition()
                .duration(500)
                .style("opacity", 0);
            tooltip.html("")
        });
    },
    redraw(svgMap) {
      svgMap.attr(
        "transform",
        "translate(" +
          this.$d3.event.translate +
          ")scale(" +
          this.$d3.event.scale +
          ")"
      );
    },
    handleCustom(data) {
      this.isPlayBtn = true;
    },
    findByID(tree, id) {
      for (let i = 0; i < tree.length; i++) {
        if (tree[i].id == id) {
          return tree[i];
        } else if (tree[i].children) {
          let result = this.findByID(tree[i].children, id);
          if (result) return result;
        }
      }
      return null;
    },
  },
};
// 获取树的深度
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 = "14px", 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>
.custom-icon {
  position: absolute;
  width: 20px;
  height: 20px;
  background-size: cover;
}
.catalogue {
  .header {
    p {
      text-align: center;
      &:nth-child(1) {
        height: 32px;
        font-family: Alibaba-PuHuiTi-B;
        font-weight: bold;
        font-size: 24px;
        color: #000000d9;
        line-height: 32px;
        margin-top: 56px;
      }
      &:nth-child(2) {
        font-family: AlibabaPuHuiTi-Regular;
        font-size: 16px;
        color: #191b2973;
        line-height: 24px;
        margin-bottom: 35px;
        margin-top: 8px;
      }
    }
  }
  .d3 {
    background: #fafafa;
    position: relative;
    overflow: hidden;
    width: calc(100%);
    min-height: 500px;
    overflow: scroll;
    padding: 24px;
    .d3-content {
      position: relative;
      width: max-content;
      ::v-deep .custom-html {
        display: flex;

        div {
          color: #000000d9;
          // width: 200px;
          // overflow: hidden; /* 确保超出容器的内容被裁剪 */
          // white-space: nowrap; /* 确保文本在一行内显示 */
          // text-overflow: ellipsis; /* 超出部分显示省略号 */
          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>

引用

<MindMap
            :data="dataList.course_knowledge_map"
            :objs="dataList"
        />
import MindMap from "@/pages/web_site_front/resource_manage/pages/onlineCourses/courseTrain/mindMap.vue";

JSON数据

let dataList={
    "title": "满汉全席(金玉满堂)",
    "children": [
        {
            "id": 2116,
            "show_id": "MC1811210229176336384",
            "detail_type": 1,
            "title": "(一)亲藩宴",
            "children": [
                {
                    "id": 2117,
                    "show_id": "MC1811215670698573824",
                    "detail_type": 1,
                    "title": "到峰点心",
                    "children": [
                        {
                            "id": 1074,
                            "show_id": "MC1811221498663006208",
                            "detail_type": 2,
                            "title": "1.1白玉奶茶",
                            "children": null
                        },
                        {
                            "id": 1077,
                            "show_id": "MC1811216950540107776",
                            "detail_type": 2,
                            "title": "1.3香酥苹果",
                            "children": [
                                {
                                    "id": 2118,
                                    "show_id": "MC1811218179148218368",
                                    "detail_type": 3,
                                    "title": "苹果",
                                    "children": null
                                }
                            ]
                        },
                        {
                            "id": 1785,
                            "show_id": "MC1811217195239997440",
                            "detail_type": 2,
                            "title": "1.4茶食刀切",
                            "children": null
                        },
                        {
                            "id": 1080,
                            "show_id": "MC1811218269577408512",
                            "detail_type": 2,
                            "title": "1.5杏仁佛手",
                            "children": [
                                {
                                    "id": 2119,
                                    "show_id": "MC1811218413282656256",
                                    "detail_type": 3,
                                    "title": "佛手",
                                    "children": null
                                },
                                {
                                    "id": 2120,
                                    "show_id": "MC1811218443401949184",
                                    "detail_type": 3,
                                    "title": "杏仁",
                                    "children": null
                                }
                            ]
                        }
                    ]
                },
                {
                    "id": 2121,
                    "show_id": "MC1813039696538333184",
                    "detail_type": 1,
                    "title": "麻婆豆腐(1)",
                    "children": [
                        {
                            "id": 2041,
                            "show_id": "MC1813039820798779392",
                            "detail_type": 2,
                            "title": "麻椒",
                            "children": null
                        },
                        {
                            "id": 2042,
                            "show_id": "MC1813039909135015936",
                            "detail_type": 2,
                            "title": "辣椒",
                            "children": null
                        },
                        {
                            "id": 2043,
                            "show_id": "MC1813039979620294656",
                            "detail_type": 2,
                            "title": "香油",
                            "children": null
                        }
                    ]
                }
            ]
        },
        {
            "id": 2122,
            "show_id": "MC1811214759129509888",
            "detail_type": 1,
            "title": "(二)延臣宴",
            "children": [
                {
                    "id": 2123,
                    "show_id": "MC1811214988734099456",
                    "detail_type": 1,
                    "title": "乾果四品",
                    "children": [
                        {
                            "id": 1085,
                            "show_id": "MC1811215309669658624",
                            "detail_type": 2,
                            "title": "2.1蜂蜜花生",
                            "children": [
                                {
                                    "id": 2124,
                                    "show_id": "MC1812661517231460352",
                                    "detail_type": 3,
                                    "title": "蜜蜂",
                                    "children": null
                                }
                            ]
                        },
                        {
                            "id": 1086,
                            "show_id": "MC1811219711507820544",
                            "detail_type": 2,
                            "title": "2.2 苹果软糖",
                            "children": null
                        },
                        {
                            "id": 1421,
                            "show_id": "MC1811572626625839104",
                            "detail_type": 2,
                            "title": "2.3乾隆白菜",
                            "children": null
                        }
                    ]
                }
            ]
        },
        {
            "id": 2125,
            "show_id": "MC1811318349517012992",
            "detail_type": 1,
            "title": "(三)万寿宴",
            "children": [
                {
                    "id": 2126,
                    "show_id": "MC1811318410309255168",
                    "detail_type": 1,
                    "title": "万",
                    "children": [
                        {
                            "id": 2127,
                            "show_id": "MC1811318467976732672",
                            "detail_type": 1,
                            "title": "寿",
                            "children": [
                                {
                                    "id": 1476,
                                    "show_id": "MC1811593045005209600",
                                    "detail_type": 2,
                                    "title": "增加时长看进度条",
                                    "children": null
                                },
                                {
                                    "id": 1412,
                                    "show_id": "MC1811395246259097600",
                                    "detail_type": 2,
                                    "title": "我测试用",
                                    "children": null
                                }
                            ]
                        }
                    ]
                }
            ]
        },
        {
            "id": 2128,
            "show_id": "MC1811660317128282112",
            "detail_type": 1,
            "title": "(四)千叟宴",
            "children": [
                {
                    "id": 1758,
                    "show_id": "MC1812680201266126848",
                    "detail_type": 2,
                    "title": "鲟龙鱼汤",
                    "children": null
                }
            ]
        },
        {
            "id": 2129,
            "show_id": "MC1811660398778798080",
            "detail_type": 1,
            "title": "(五)节令宴",
            "children": [
                {
                    "id": 2130,
                    "show_id": "MC1812758268982456320",
                    "detail_type": 1,
                    "title": "1",
                    "children": [
                        {
                            "id": 2131,
                            "show_id": "MC1812758643072430080",
                            "detail_type": 1,
                            "title": "2",
                            "children": [
                                {
                                    "id": 2132,
                                    "show_id": "MC1812758669962108928",
                                    "detail_type": 1,
                                    "title": "3",
                                    "children": [
                                        {
                                            "id": 1856,
                                            "show_id": "MC1812759373250424832",
                                            "detail_type": 2,
                                            "title": "1111",
                                            "children": null
                                        }
                                    ]
                                },
                                {
                                    "id": 2133,
                                    "show_id": "MC1813050911662628864",
                                    "detail_type": 1,
                                    "title": "3.1",
                                    "children": [
                                        {
                                            "id": 2107,
                                            "show_id": "MC1813051162096136192",
                                            "detail_type": 2,
                                            "title": "333333",
                                            "children": null
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ]
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值