vue3+d3实现股权穿透图

效果图

1.安装d3.js 

npm install d3

2.使用 components/EquityChart/index.vue (按钮组图标去阿里矢量图标库下载图片至 images)

<template>
  <div id="borrow">
    <!-- 操作按钮组 -->
    <div class="functionBox">
      <div class="functionItem" @click="toggleFullScreen">
        <el-tooltip
          class="box-item"
          effect="dark"
          content="全屏"
          placement="left"
        >
          <img src="./images/iconFull.webp" alt="" />
        </el-tooltip>
      </div>
      <div class="functionItem" @click="expandAllNodes()">
        <el-tooltip
          class="box-item"
          effect="dark"
          content="全部展开"
          placement="left"
        >
          <img src="./images/iconPlus.webp" alt="" />
        </el-tooltip>
      </div>
      <div class="functionItem" @click="foldAllNodes()">
        <el-tooltip
          class="box-item"
          effect="dark"
          content="全部收起"
          placement="left"
        >
          <img src="./images/iconMinus.webp" alt="" />
        </el-tooltip>
      </div>
      <div class="functionItem" @click="foldAllNodes()">
        <el-tooltip
          class="box-item"
          effect="dark"
          content="刷新"
          placement="left"
        >
          <img src="./images/iconRefresh.webp" alt="" />
        </el-tooltip>
      </div>
      <div class="functionItem" @click="downloadImpByChart">
        <el-tooltip
          class="box-item"
          effect="dark"
          content="下载"
          placement="left"
        >
          <img src="./images/iconDownload.webp" alt="" />
        </el-tooltip>
      </div>
    </div>
    <!-- 绘制架构图盒子 -->
    <div id="appc" ref="appc" :style="styles"></div>
  </div>
</template>
<script>
export default {
  name: "EquityChart",
};
</script>
<script setup>
import * as d3 from "d3";
import { onMounted, ref } from "vue";
import { ElTooltip } from "element-plus";
const props = defineProps({
  styles: {
    type: Object,
    default: () => {
      return {
        height: "650px",
      };
    },
  },
  data: {
    type: Object,
    default: () => {},
  },
});

//一些配置项
const config = ref({
  // 节点的横向距离
  dx: 200,
  // 节点的纵向距离
  dy: 170,
  // svg的viewBox的宽度
  width: 0,
  // svg的viewBox的高度
  height: 650,
  // 节点的矩形框宽度
  rectWidth: 170,
  // 节点的矩形框高度
  rectHeight: 70,
});

const svgs = ref(null);
const gAlls = ref(null);
const gLinks = ref(null);
const gNodes = ref(null);
// 给树加坐标点的方法
const tree = ref(null);
// 投资公司树的根节点
const rootOfDown = ref(null);
// 股东树的根节点
const rootOfUp = ref(null);
//是否全屏
const isFullscreen = ref(true);
onMounted(() => {
  drawChart({
    type: "fold",
  });
  window.addEventListener("resize", function () {
    //ESC退出全屏改变图表
    if (!checkFull()) {
      isFullscreen.value = true;
    }
    const svg = document.getElementById("mysvg");
    svg.setAttribute("height", window.innerHeight);
  });
});

// 初始化树结构数据
function drawChart(options) {
  // 宿主元素的d3选择器对象
  let host = d3.select(document.getElementById("appc"));
  // 宿主元素的DOM,通过node()获取到其DOM元素对象
  let dom = host.node();
  // 宿主元素的DOMRect
  let domRect = dom.getBoundingClientRect();
  // svg的宽度和高度
  config.value.width = domRect.width;
  config.value.height = domRect.height;
  let oldSvg = host.select("svg");
  // 如果宿主元素中包含svg标签了,那么则删除这个标签,再重新生成一个
  if (!oldSvg.empty()) {
    oldSvg.remove();
  }
  const svg = d3
    .create("svg")
    .attr("id", "mysvg")
    .attr("viewBox", () => {
      let parentsLength = props.data.parents ? props.data.parents.length : 0;
      return [
        -config.value.width / 2,
        // 如果有父节点,则根节点居中,否则根节点上浮一段距离
        parentsLength > 0 ? -config.value.height / 2 : -config.value.height / 3,
        config.value.width,
        config.value.height,
      ];
    })
    .style("user-select", "none")
    .style("cursor", "move");

  // 包括连接线和节点的总集合
  const gAll = svg.append("g").attr("id", "all");
  svg
    .call(
      d3
        .zoom()
        .scaleExtent([0.5, 2])
        .on("zoom", (e) => {
          gAll.attr("transform", () => {
            return `translate(${e.transform.x},${e.transform.y}) scale(${e.transform.k})`;
          });
        })
    )
    .on("dblclick.zoom", null); // 取消默认的双击放大事件
  gAlls.value = gAll;
  // 连接线集合
  gLinks.value = gAll.append("g").attr("id", "linkGroup");
  // 节点集合
  gNodes.value = gAll.append("g").attr("id", "nodeGroup");
  // 设置好节点之间距离的tree方法
  tree.value = d3.tree().nodeSize([config.value.dx, config.value.dy]);
  rootOfDown.value = d3.hierarchy(props.data, (d) => d.children);
  rootOfUp.value = d3.hierarchy(props.data, (d) => d.parents);
  tree.value(rootOfDown.value);
  [rootOfDown.value.descendants(), rootOfUp.value.descendants()].forEach(
    (nodes) => {
      nodes.forEach((node) => {
        node._children = node.children || null;
        if (options.type === "all") {
          //如果是all的话,则表示全部都展开
          node.children = node._children;
        } else if (options.type === "fold") {
          //如果是fold则表示除了父节点全都折叠
          // 将非根节点的节点都隐藏掉(其实对于这个组件来说加不加都一样)
          if (node.depth) {
            node.children = null;
          }
        }
      });
    }
  );
  //箭头(下半部分)
  svg
    .append("marker")
    .attr("id", "markerOfDown")
    .attr("markerUnits", "userSpaceOnUse")
    .attr("viewBox", "0 -5 10 10") //坐标系的区域
    .attr("refX", 55) //箭头坐标
    .attr("refY", 0)
    .attr("markerWidth", 10) //标识的大小
    .attr("markerHeight", 10)
    .attr("orient", "90") //绘制方向,可设定为:auto(自动确认方向)和 角度值
    .attr("stroke-width", 2) //箭头宽度
    .append("path")
    .attr("d", "M0,-5L10,0L0,5") //箭头的路径
    .attr("fill", "#215af3"); //箭头颜色
  //箭头(上半部分)
  svg
    .append("marker")
    .attr("id", "markerOfUp")
    .attr("markerUnits", "userSpaceOnUse")
    .attr("viewBox", "0 -5 10 10") //坐标系的区域
    .attr("refX", -50) //箭头坐标
    .attr("refY", 0)
    .attr("markerWidth", 10) //标识的大小
    .attr("markerHeight", 10)
    .attr("orient", "90") //绘制方向,可设定为:auto(自动确认方向)和 角度值
    .attr("stroke-width", 2) //箭头宽度
    .append("path")
    .attr("d", "M0,-5L10,0L0,5") //箭头的路径
    .attr("fill", "#215af3"); //箭头颜色
  svgs.value = svg;
  update();
  // 将svg置入宿主元素中
  host.append(function () {
    return svg.node();
  });
}
// 更新数据
function update(source) {
  if (!source) {
    source = {
      x0: 0,
      y0: 0,
    };
    // 设置根节点所在的位置(原点)
    rootOfDown.value.x0 = 0;
    rootOfDown.value.y0 = 0;
    rootOfUp.value.x0 = 0;
    rootOfUp.value.y0 = 0;
  }
  let nodesOfDown = rootOfDown.value.descendants().reverse();
  let linksOfDown = rootOfDown.value.links();
  let nodesOfUp = rootOfUp.value.descendants().reverse();
  let linksOfUp = rootOfUp.value.links();
  tree.value(rootOfDown.value);
  tree.value(rootOfUp.value);
  const myTransition = svgs.value.transition().duration(500);
  /***  绘制子公司树(下)  ***/
  const node1 = gNodes.value
    .selectAll("g.nodeOfDownItemGroup")
    .data(nodesOfDown, (d) => {
      return d.data.id;
    });
  const node1Enter = node1
    .enter()
    .append("g")
    .attr("class", "nodeOfDownItemGroup")
    .attr("transform", (d) => {
      return `translate(${source.x0},${source.y0})`;
    })
    .attr("fill-opacity", 0)
    .attr("stroke-opacity", 0)
    .style("cursor", "pointer");
  // 外层的矩形框
  node1Enter
    .append("rect")
    .attr("width", (d) => {
      if (d.depth === 0) {
        return (d.data.name.length + 2) * 16;
      }
      return config.value.rectWidth;
    })
    .attr("height", (d) => {
      if (d.depth === 0) {
        return 40;
      }
      return config.value.rectHeight;
    })
    .attr("x", (d) => {
      if (d.depth === 0) {
        return (-(d.data.name.length + 2) * 16) / 2;
      }
      return -config.value.rectWidth / 2;
    })
    .attr("y", (d) => {
      if (d.depth === 0) {
        return -15;
      }
      return -config.value.rectHeight / 2;
    })
    .attr("rx", 5)
    .attr("stroke-width", 1)
    .attr("stroke", (d) => {
      if (d.depth === 0) {
        return "#5682ec";
      }
      return "#7A9EFF";
    })
    .attr("fill", (d) => {
      if (d.depth === 0) {
        return "#7A9EFF";
      }
      return "#FFFFFF";
    })
    .on("click", (e, d) => {
      nodeClickEvent(e, d);
    });
  // 文本主标题
  node1Enter
    .append("text")
    .attr("class", "main-title")
    .attr("x", (d) => {
      return 0;
    })
    .attr("y", (d) => {
      if (d.depth === 0) {
        return 10;
      }
      return -14;
    })
    .attr("text-anchor", (d) => {
      return "middle";
    })
    .text((d) => {
      if (d.depth === 0) {
        return d.data.name;
      } else {
        return d.data.name.length > 11
          ? d.data.name.substring(0, 11)
          : d.data.name;
      }
    })
    .attr("fill", (d) => {
      if (d.depth === 0) {
        return "#FFFFFF";
      }
      return "#000000";
    })
    .style("font-size", (d) => (d.depth === 0 ? 16 : 14))
    .style("font-family", "黑体")
    .style("font-weight", "bold")
    .append("svg:title")
    .text((d) => d.data.name);

  // 副标题
  node1Enter
    .append("text")
    .attr("class", "sub-title")
    .attr("x", (d) => {
      return 0;
    })
    .attr("y", (d) => {
      return 5;
    })
    .attr("text-anchor", (d) => {
      return "middle";
    })
    .text((d) => {
      if (d.depth !== 0) {
        let subTitle = d.data.name.substring(11);
        if (subTitle.length > 10) {
          return subTitle.substring(0, 10) + "...";
        } else {
          return subTitle;
        }
      }
    })
    .style("font-size", (d) => 14)
    .style("font-family", "黑体")
    .style("font-weight", "bold")
    .append("svg:title")
    .text((d) => d.data.name);
  //融资轮次
  node1Enter
    .append("svg:rect")
    .attr("x", "-84")
    .attr("y", "14")
    .attr("rx", 5)
    .attr("width", (d) => {
      if (d.depth !== 0 && d.data.rounds) {
        return config.value.rectWidth - 2;
      }
    })
    .attr("height", "20")
    .style("fill", "#E5F3FE");
  node1Enter
    .append("text")
    .attr("x", "0")
    .attr("dy", "30")
    .attr("text-anchor", "middle")

    .attr("class", "linkname")
    // .style("fill", "#666")
    .style("font-size", 12)
    .attr("fill", "#008BF8")
    .text(function (d) {
      if (d.depth !== 0 && d.data.rounds) {
        var str = "融资轮次:" + d.data.rounds;
        return str.length > 13 ? str.substring(0, 18) + ".." : str;
      }
    });

  // 控股比例
  node1Enter
    .append("text")
    .attr("class", "percent")
    .attr("x", (d) => {
      return 15;
    })
    .attr("y", (d) => {
      return -55;
    })
    .text((d) => {
      if (d.depth !== 0) {
        return d.data.percent;
      }
    })
    .attr("fill", "#0084FF")
    .style("font-family", "黑体")
    .style("font-size", (d) => 14);
  node1Enter
    .append("svg:rect")
    .attr("x", "-40")
    .attr("y", "-70")
    .attr("rx", 2)
    .attr("width", function (d) {
      return d.depth !== 0 && d.data.percent ? 30 : 0;
    })
    .attr("height", function (d) {
      return d.depth !== 0 && d.data.percent ? 20 : 0;
    })
    .style("fill", "#EBF5FF");
  node1Enter
    .append("text")
    .attr("x", "-37")
    .attr("dy", "-55")
    .attr("text-anchor", "start")
    .style("fill", "#0084FF")
    .style("font-size", 12)
    .text(function (d) {
      return d.depth !== 0 && d.data.percent ? "控股" : "";
    });
  //吊销/注销
  node1Enter
    .append("svg:rect")
    .attr("x", "10")
    .attr("y", "-48")
    .attr("rx", 2)
    .attr("width", function (d) {
      return d.depth !== 0 && d.data.off ? 45 : 0;
    })
    .attr("height", function (d) {
      return d.depth !== 0 && d.data.off ? 10 : 0;
    })
    .style("fill", "#fde2e2");
  node1Enter
    .append("text")
    .attr("x", "15")
    .attr("dy", "-40")
    .attr("text-anchor", "start")
    .style("fill", "#f89898")
    .style("font-size", 8)
    .text(function (d) {
      return d.depth !== 0 && d.data.off ? "吊销/注销" : "";
    });
  // 增加展开按钮
  const expandBtnG = node1Enter
    .append("g")
    .attr("class", "expandBtn")
    .attr("transform", (d) => {
      return `translate(${0},${config.value.rectHeight / 2})`;
    })
    .style("display", (d) => {
      // 如果是根节点,不显示
      if (d.depth === 0) {
        return "none";
      }
      // 如果没有子节点,则不显示
      if (!d._children) {
        return "none";
      }
    })
    .on("click", (e, d) => {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
      }
      update(d);
    });

  expandBtnG
    .append("circle")
    .attr("r", 8)
    .attr("fill", "#7A9EFF")
    .attr("cy", 8);

  expandBtnG
    .append("text")
    .attr("text-anchor", "middle")
    .attr("fill", "#ffffff")
    .attr("y", 13)
    .style("font-size", 16)
    .style("font-family", "微软雅黑")
    .text((d) => {
      return d.children ? "-" : "+";
    });

  const link1 = gLinks.value
    .selectAll("path.linkOfDownItem")
    .data(linksOfDown, (d) => d.target.data.id);

  const link1Enter = link1
    .enter()
    .append("path")
    .attr("class", "linkOfDownItem")
    .attr("d", (d) => {
      let o = {
        source: {
          x: source.x0,
          y: source.y0,
        },
        target: {
          x: source.x0,
          y: source.y0,
        },
      };
      return drawLink(o);
    })
    .attr("fill", "none")
    .attr("stroke", "#7A9EFF")
    .attr("stroke-width", 1)
    .attr("marker-end", "url(#markerOfDown)");

  // 有元素update更新和元素新增enter的时候
  node1
    .merge(node1Enter)
    .transition(myTransition)
    .attr("transform", (d) => {
      return `translate(${d.x},${d.y})`;
    })
    .attr("fill-opacity", 1)
    .attr("stroke-opacity", 1);

  // 有元素消失时
  node1
    .exit()
    .transition(myTransition)
    .remove()
    .attr("transform", (d) => {
      return `translate(${source.x0},${source.y0})`;
    })
    .attr("fill-opacity", 0)
    .attr("stroke-opacity", 0);

  link1.merge(link1Enter).transition(myTransition).attr("d", drawLink);

  link1
    .exit()
    .transition(myTransition)
    .remove()
    .attr("d", (d) => {
      let o = {
        source: {
          x: source.x,
          y: source.y,
        },
        target: {
          x: source.x,
          y: source.y,
        },
      };
      return drawLink(o);
    });

  /***  绘制股东树(上)  ***/

  nodesOfUp.forEach((node) => {
    node.y = -node.y;
  });

  const node2 = gNodes.value
    .selectAll("g.nodeOfUpItemGroup")
    .data(nodesOfUp, (d) => {
      return d.data.id;
    });

  const node2Enter = node2
    .enter()
    .append("g")
    .attr("class", "nodeOfUpItemGroup")
    .attr("transform", (d) => {
      return `translate(${source.x0},${source.y0})`;
    })
    .attr("fill-opacity", 0)
    .attr("stroke-opacity", 0)
    .style("cursor", "pointer");

  // 外层的矩形框
  node2Enter
    .append("rect")
    .attr("width", (d) => {
      if (d.depth === 0) {
        return (d.data.name.length + 2) * 16;
      }
      return config.value.rectWidth;
    })
    .attr("height", (d) => {
      if (d.depth === 0) {
        return 40;
      }
      return config.value.rectHeight;
    })
    .attr("x", (d) => {
      if (d.depth === 0) {
        return (-(d.data.name.length + 2) * 16) / 2;
      }
      return -config.value.rectWidth / 2;
    })
    .attr("y", (d) => {
      if (d.depth === 0) {
        return -15;
      }
      return -config.value.rectHeight / 2;
    })
    .attr("rx", 5)
    .attr("stroke-width", 1)
    .attr("stroke", (d) => {
      if (d.depth === 0) {
        return "#5682ec";
      }
      return "#7A9EFF";
    })
    .attr("fill", (d) => {
      if (d.depth === 0) {
        return "#7A9EFF";
      }
      return "#FFFFFF";
    })
    .on("click", (e, d) => {
      nodeClickEvent(e, d);
    });
  // 文本主标题
  node2Enter
    .append("text")
    .attr("class", "main-title")
    .attr("x", (d) => {
      return 0;
    })
    .attr("y", (d) => {
      if (d.depth === 0) {
        return 10;
      }
      return -14;
    })
    .attr("text-anchor", (d) => {
      return "middle";
    })
    .text((d) => {
      if (d.depth === 0) {
        return d.data.name;
      } else {
        return d.data.name.length > 11
          ? d.data.name.substring(0, 11)
          : d.data.name;
      }
    })
    .attr("fill", (d) => {
      if (d.depth === 0) {
        return "#FFFFFF";
      }
      return "#000000";
    })
    .style("font-size", (d) => (d.depth === 0 ? 16 : 14))
    .style("font-family", "黑体")
    .style("font-weight", "bold")
    .append("svg:title")
    .text((d) => d.data.name);
  // 副标题
  node2Enter
    .append("text")
    .attr("class", "sub-title")
    .attr("x", (d) => {
      return 0;
    })
    .attr("y", (d) => {
      return 5;
    })
    .attr("text-anchor", (d) => {
      return "middle";
    })
    .text((d) => {
      if (d.depth !== 0) {
        let subTitle = d.data.name.substring(11);
        if (subTitle.length > 10) {
          return subTitle.substring(0, 10) + "...";
        }
        return subTitle;
      }
    })
    .style("font-size", (d) => 14)
    .style("font-family", "黑体")
    .style("font-weight", "bold")
    .append("svg:title")
    .text((d) => d.data.name);

  // 控股比例
  node2Enter
    .append("text")
    .attr("class", "percent")
    .attr("x", (d) => {
      return 12;
    })
    .attr("y", (d) => {
      return 55;
    })
    .text((d) => {
      if (d.depth !== 0) {
        return d.data.percent;
      }
    })
    .attr("fill", function (d) {
      return d.data.controlling ? "#FA6B64" : "#000000";
    })
    .style("font-family", "黑体")
    .style("font-size", (d) => 14);
  //地址
  node2Enter
    .append("svg:rect")
    .attr("x", "50")
    .attr("y", "-55")
    .attr("rx", 2)
    .attr("width", function (d) {
      return d.depth !== 0 && d.data.address ? 28 : 0;
    })
    .attr("height", function (d) {
      return d.depth !== 0 && d.data.address ? 15 : 0;
    })
    .style("fill", " #e1f3d8");
  node2Enter
    .append("text")
    .attr("x", "53")
    .attr("dy", "-44")
    .attr("text-anchor", "start")
    .style("fill", "#95d475")
    .style("font-size", 10)
    .text(function (d) {
      return d.depth !== 0 && d.data.address ? d.data.address : "";
    });
  //实际控股人
  node2Enter
    .append("svg:rect")
    .attr("x", -50)
    .attr("y", -90)
    .attr("width", function (d) {
      return d.data.controlling ? 100 : 0;
    })
    .attr("height", function (d) {
      return d.data.controlling ? 40 : 0;
    })
    .attr("rx", 2)
    .style("stroke", function (d) {
      return d.data.controlling ? "#FA6B64" : "#F1B03A";
    })
    .style("fill", function (d) {
      return d.data.controlling ? "#FA6B64" : "#F1B03A"; //节点背景色
    });
  node2Enter
    .append("svg:path")
    .attr("fill", (d) => {
      return d.data.controlling ? "#FA6B64" : "#F1B03A";
    })
    .attr("d", function (d) {
      if (d.data.controlling) {
        return "M0 -44 L-10 -54 L10 -54 Z";
      } else {
        return "";
      }
    });
  node2Enter
    .append("text")
    .attr("x", "0")
    .attr("dy", "-74")
    .attr("text-anchor", "middle")
    .style("fill", "#fff")
    .style("font-size", 12)
    .text(function (d) {
      return d.data.controlling ? "实际控制人" : "";
    });
  node2Enter
    .append("text")
    .attr("x", "0")
    .attr("dy", "-58")
    .attr("text-anchor", "middle")
    .style("fill", "#fff")
    .style("font-size", 12)
    .text(function (d) {
      return d.data.controlling ? "受益所有人" : "";
    });
  // 增加展开按钮
  const expandBtnG2 = node2Enter
    .append("g")
    .attr("class", "expandBtn")
    .attr("transform", (d) => {
      return `translate(${0},${-config.value.rectHeight / 2})`;
    })
    .style("display", (d) => {
      // 如果是根节点,不显示
      if (d.depth === 0) {
        return "none";
      }
      // 如果没有子节点,则不显示
      if (!d._children) {
        return "none";
      }
    })
    .on("click", (e, d) => {
      if (d.children) {
        d._children = d.children;
        d.children = null;
      } else {
        d.children = d._children;
      }
      update(d);
    });

  expandBtnG2
    .append("circle")
    .attr("r", 8)
    .attr("fill", "#7A9EFF")
    .attr("cy", -8);

  expandBtnG2
    .append("text")
    .attr("text-anchor", "middle")
    .attr("fill", "#ffffff")
    .attr("y", -3)
    .style("font-size", 16)
    .style("font-family", "微软雅黑")
    .text((d) => {
      return d.children ? "-" : "+";
    });

  const link2 = gLinks.value
    .selectAll("path.linkOfUpItem")
    .data(linksOfUp, (d) => d.target.data.id);

  const link2Enter = link2
    .enter()
    .append("path")
    .attr("class", "linkOfUpItem")
    .attr("d", (d) => {
      let o = {
        source: {
          x: source.x0,
          y: source.y0,
        },
        target: {
          x: source.x0,
          y: source.y0,
        },
      };
      return drawLink(o);
    })
    .attr("fill", "none")
    .attr("stroke", "#7A9EFF")
    .attr("stroke-width", 1)
    .attr("marker-end", "url(#markerOfUp)");

  // 有元素update更新和元素新增enter的时候
  node2
    .merge(node2Enter)
    .transition(myTransition)
    .attr("transform", (d) => {
      return `translate(${d.x},${d.y})`;
    })
    .attr("fill-opacity", 1)
    .attr("stroke-opacity", 1);
  // 有元素消失时
  node2
    .exit()
    .transition(myTransition)
    .remove()
    .attr("transform", (d) => {
      return `translate(${source.x0},${source.y0})`;
    })
    .attr("fill-opacity", 0)
    .attr("stroke-opacity", 0);
  link2.merge(link2Enter).transition(myTransition).attr("d", drawLink);
  link2
    .exit()
    .transition(myTransition)
    .remove()
    .attr("d", (d) => {
      let o = {
        source: {
          x: source.x,
          y: source.y,
        },
        target: {
          x: source.x,
          y: source.y,
        },
      };
      return drawLink(o);
    });
  // node数据改变的时候更改一下加减号
  const expandButtonsSelection = d3.selectAll("g.expandBtn");
  expandButtonsSelection
    .select("text")
    .transition()
    .text((d) => {
      return d.children ? "-" : "+";
    });

  rootOfDown.value.eachBefore((d) => {
    d.x0 = d.x;
    d.y0 = d.y;
  });
  rootOfUp.value.eachBefore((d) => {
    d.x0 = d.x;
    d.y0 = d.y;
  });
}
// 直角连接线 by wushengyuan
function drawLink({ source, target }) {
  const halfDistance = (target.y - source.y) / 2;
  const halfY = source.y + halfDistance;
  return `M${source.x},${source.y} L${source.x},${halfY} ${target.x},${halfY} ${target.x},${target.y}`;
}
// 展开所有的节点
function expandAllNodes() {
  drawChart({
    type: "all",
  });
}
// 将所有节点都折叠
function foldAllNodes() {
  drawChart({
    type: "fold",
  });
}
//点击节点获取节点数据
function nodeClickEvent(e, d) {
  // console.log('当前节点的数据:', d)
}
//全屏-退出全屏
function toggleFullScreen(e) {
  isFullscreen.value = !isFullscreen.value;
  FullScreen(document.getElementById("borrow"));
}
//全屏
function fullele() {
  return (
    document.fullscreenElement ||
    document.webkitFullscreenElement ||
    document.msFullscreenElement ||
    document.mozFullScreenElement ||
    null
  );
}
//判断是否为全屏
function checkFull() {
  return !!(document.webkitIsFullScreen || fullele());
}
//全屏-退出全屏
function FullScreen(el) {
  if (isFullscreen.value) {
    //退出全屏
    if (document.exitFullscreen) {
      document.exitFullscreen();
    } else if (document.mozCancelFullScreen) {
      document.mozCancelFullScreen();
    } else if (document.webkitExitFullscreen) {
      document.webkitExitFullscreen();
    } else if (!document.msRequestFullscreen) {
      document.msExitFullscreen();
    }
  } else {
    //进入全屏
    if (el.requestFullscreen) {
      el.requestFullscreen();
    } else if (el.mozRequestFullScreen) {
      el.mozRequestFullScreen();
    } else if (el.webkitRequestFullscreen) {
      //改变平面图在google浏览器上面的样式问题
      el.webkitRequestFullscreen();
    } else if (el.msRequestFullscreen) {
      isFullscreen.value = true;
      el.msRequestFullscreen();
    }
  }
}
//下载为图片
function downloadImpByChart(chartName) {
  //得到svg的真实大小
  let box = document.querySelector("svg").getBBox(),
    x = box.x,
    y = box.y,
    width = box.width * 2,
    height = box.height * 2;
  //克隆svg
  var node = svg.cloneNode(true);
  //重新设置svg的width,height,viewbox
  node.setAttribute("width", width * 2);
  node.setAttribute("height", height * 2);
  node.setAttribute("viewBox", [x, y, width, height]);
  downloadSvgFn(node, width, height, props.data.name);
}
//下载
function downloadSvgFn(svg, width, height, rootName) {
  var serializer = new XMLSerializer();
  var source =
    '<?xml version="1.0" standalone="no"?>\r\n' +
    serializer.serializeToString(svg);
  var image = new Image();
  image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
  image.onload = function () {
    var canvas = document.createElement("canvas");
    canvas.width = width + 40;
    canvas.height = height + 40;
    var context = canvas.getContext("2d");
    context.rect(0, 0, canvas.width, canvas.height);
    context.fillStyle = "#fff";
    context.fill();
    context.drawImage(image, 20, 20);
    var url = canvas.toDataURL("image/png");
    var a = document.createElement("a");
    a.download = `${rootName}-股权穿透图.png`;
    a.href = url;
    a.click();
    return;
  };
}
</script>

<style lang="scss" scoped>
#borrow {
  background: #fff;
  position: relative;
}
.functionBox {
  padding: 5px;
  position: absolute;
  right: 10px;
  bottom: 45%;
  display: flex;
  flex-direction: column;
  background: #f8f8f8;
  transition: all linear 0.2s;
  border-radius: 8px;

  .functionItem {
    color: #444;
    height: 35px;
    width: 35px;
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    border-radius: 4px;
  }
  functionItem > img {
    width: 100%;
    height: 100%;
  }
  .functionItem:hover {
    background: rgb(233, 233, 233);
  }
}
</style>

3.调用 views/HomeView.vue

<template>
  <div class="main">
    <EquityChart :styles="styles" :data="chartData"></EquityChart>
  </div>
</template>
<script setup>
import EquityChart from "@/components/EquityChart/index.vue";
import { ref } from "vue";
const styles = ref({
  height: "650px",
});
//股权树数据
const chartData = ref({
  id: "abc1005",
  // 根节点名称
  name: "上海XXX股份有限公司",
  // 子节点列表
  children: [
    {
      id: "abc1006",
      name: "深圳市XXX投资有限公司深圳市XXX投资有限公司深圳市XXX投资有限公司",
      percent: "100%", // 控股比例
      rounds: "天使轮", // 融资轮次
    },
    {
      id: "abc1007",
      name: "山东XXX技术有限公司",
      percent: "100%",
      rounds: "A轮",
    },
    {
      id: "abc1008",
      name: "长沙XXX光伏材料有限公司",
      percent: "100%",
      rounds: "被收购",
      off: true, // 吊销/注销
    },
    {
      id: "abc1009",
      name: "绍兴XXX科技发展有限公司",
      percent: "100%",
      children: [
        {
          id: "abc1010",
          name: "绍兴XXX分析仪器有限公司",
          percent: "100%",
          children: [
            {
              id: "abc1011",
              name: "绍兴XXX的子公司一",
              percent: "80%",
            },
            {
              id: "abc1012",
              name: "绍兴XXX的子公司二",
              percent: "90%",
            },
            {
              id: "abc1013",
              name: "绍兴XXX的子公司三",
              percent: "100%",
            },
          ],
        },
      ],
    },
    {
      id: "abc1014",
      name: "湖南XXX电工科技有限公司",
      percent: "100%",
      children: [
        {
          id: "abc1015",
          name: "湖南XXX自动化设备有限公司",
          percent: "100%",
          children: [
            {
              id: "abc1016",
              name: "湖南XXX的子公司一",
              percent: "100%",
            },
            {
              id: "abc1017",
              name: "湖南XXX的子公司二",
              percent: "90%",
            },
          ],
        },
      ],
    },
    {
      id: "abc1020",
      name: "上海XXX分析仪器(集团)有限责任公司",
      percent: "100%",
      children: [
        {
          id: "abc1021",
          name: "上海XXX分气体工业有限公司",
        },
      ],
    },
  ],
  // 父节点列表
  parents: [
    {
      id: "abc2001",
      name: "香港XX集团有限责任公司",
      percent: "60%",
      address: "香港", //地址
      parents: [
        {
          id: "abc2000",
          name: "香港XX集团有限公司",
          percent: "100%",
          address: "香港",
        },
      ],
    },
    {
      id: "abc2002",
      name: "上海XXX集团",
      percent: "40%",
      parents: [
        {
          id: "abc1010",
          name: "上海XXX有限公司",
          percent: "100%",
          parents: [
            {
              id: "abc1011",
              name: "上海XXX的子公司一",
              percent: "80%",
            },
            {
              id: "abc1012",
              name: "上海XXX的子公司二",
              percent: "90%",
            },
            {
              id: "abc1013",
              name: "上海XXX的子公司三",
              percent: "100%",
            },
          ],
        },
      ],
    },
    {
      id: "abc2003",
      name: "张三",
      percent: "40%",
      controlling: true, //实际控股人
    },
  ],
});
</script>
<style scoped>
.main {
  width: 100%;
  height: 100%;
}
</style>

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值