1、安装教程

npm install --save relation-graph
        如果下载引用时报min.js引用错误则需要在vue.config.js进行配置
    configureWebpack: {
        module: {
            rules: [{
                test: /\.mjs$/,
                include: /node_modules/,
                type: 'javascript/auto'
            }]
        }
    },

2、使用案列

<template>
   <div>
     <div style="height:calc(100vh - 60px);">
        <RelationGraph ref="graphRef" :options="graphOptions" :on-node-click="onNodeClick" :on-line-click="onLineClick" />
     </div>
   </div>
 </template>

 <script>
// relation-graph也支持在main.js文件中使用Vue.use(RelationGraph);这样,你就不需要下面这一行代码来引入了。
 import RelationGraph from 'relation-graph'
 export default {
   name: 'Demo',
   components: { RelationGraph },
   data() {
     return {
       graphOptions: {
         defaultJunctionPoint: 'border'
         // 这里可以参考"Graph 图谱"中的参数进行设置 https://www.relation-graph.com/#/docs/graph
       }
     }
   },
   mounted() {
     this.showGraph()
   },
   methods: {
     showGraph() {
       const jsonData = {
         rootId: 'a',
         nodes: [
           { id: 'a', text: 'A', borderColor: 'yellow' },
           { id: 'b', text: 'B', color: '#43a2f1', fontColor: 'yellow' },
           { id: 'c', text: 'C', nodeShape: 1, width: 80, height: 60 },
           { id: 'e', text: 'E', nodeShape: 0, width: 150, height: 150 }
         ],
         lines: [
           { from: 'a', to: 'b', text: '关系1', color: '#43a2f1' },
           { from: 'a', to: 'c', text: '关系2' },
           { from: 'a', to: 'e', text: '关系3' },
           { from: 'b', to: 'e', color: '#67C23A' }
         ]
       }
       // 以上数据中的node和link可以参考"Node节点"和"Link关系"中的参数进行配置
       this.$refs.graphRef.setJsonData(jsonData, (graphInstance) => {
         // Called when the relation-graph is completed
       })
     },
     onNodeClick(nodeObject, $event) {
       console.log('onNodeClick:', nodeObject)
     },
     onLineClick(lineObject, $event) {
       console.log('onLineClick:', lineObject)
     }
   }
 }
 </script>
3、个别特殊算法案列(非正规层级树形,一个节点有多个父亲,线条箭头向下一种方向延申、可点击重新渲染相关关系如有节点及线条,变更颜色等)
<template>
  <div>
    <div
      v-if="nodeList?.length > 0"
      ref="myPage"
      class="my-graph"
      style="height: calc(80vh)"
    >
      <RelationGraph
        ref="graphRef"
        :options="graphOptions"
        :on-node-click="onNodeClick"
        :on-line-click="onLineClick"
      >
        <template #node="{ node }">
          <div
            @mouseover="showNodeTips(node, $event)"
            @mouseout="hideNodeTips(node, $event)"
          >
            <div class="c-my-rg-node">
              <i style="font-size: 30px" :class="node.data.myicon" />
            </div>
            <!--  width: 160px; -->
            <div
              v-show="value1"
              style="
                font-size: 16px;
                position: absolute;
                height: 25px;
                line-height: 25px;
                margin-top: 20px;
                margin-left: -48px;
                text-align: left;
              "
            >
              <span style="color: #212026">{{ node.data.text }}</span>
              <br />
              <span style="color: #71747a">{{ node.data.tableDesc }}</span>
            </div>
          </div>
        </template>
      </RelationGraph>
      <div
        v-if="isShowNodeTipsPanel"
        :style="{
          left: nodeMenuPanelPosition.x + 'px',
          top: nodeMenuPanelPosition.y + 'px',
        }"
        style="
          z-index: 999;
          padding: 10px;
          background-color: #ffffff;
          border: #eeeeee solid 1px;
          box-shadow: 0px 0px 8px #cccccc;
          position: absolute;
        "
      >
        <div class="c-node-menu-item">
          <span
            style="
              display: inline-block;
              border-left: 2px solid #3c91ff;
              padding-left: 4px;
              color: #212026;
            "
            >{{ currentNode?.data?.text }}</span
          >
        </div>
        <div class="c-node-menu-item">
          <span
            style="display: inline-block; padding-left: 4px; color: #71747a"
            >{{ currentNode?.data?.tableDesc }}</span
          >
        </div>
      </div>
    </div>
    <div
      v-else
      style="
        background: #fff;
        font-size: 22px;
        color: #ccc;
        text-align: center;
        height: calc(70vh);
        line-height: calc(70vh);
      "
    >
      暂无数据
    </div>
  </div>
</template>

<script>
// 如果您没有在main.js文件中使用Vue.use(RelationGraph); 就需要使用下面这一行代码来引入relation-graph
import RelationGraph from "relation-graph";
import dagre from "dagre";
export default {
  name: "TreeRelation",
  components: { RelationGraph },
  data() {
    return {
      edgeGroupSign: null,
      currentNode: {},
      linesList: [],
      nodeList: [],
      isShowNodeTipsPanel: false,
      isShowCodePanel: false,
      nodeMenuPanelPosition: { x: 0, y: 0 },
      graphOptions: {
        disableLineClickEffect: true,
        // moveToCenterWhenRefresh: true,
        // debug: false,
        allowSwitchLineShape: true,
        allowSwitchJunctionPoint: true,
        // allowShowDownloadButton: true,
        useAnimationWhenRefresh: true,
        defaultLineWidth: 2,
        defaultLineColor: "transparent",
        lineUseTextPath: true,
        layout: {
          layoutName: "fixed",
          layoutDirection: "v",
        },
        defaultNodeShape: 1,
        defaultLineShape: 1,
        defaultJunctionPoint: "tb",
        defaultPloyLineRadius: 10,
        defaultNodeBorderWidth: 0,
        defaultPolyLineRadius: 20,
        defaultNodeWidth: 54,
        defaultNodeHeight: 54,
        toolBarDirection: "h",
        toolBarPositionH: "left",
        toolBarPositionV: "top",
        // defaultNodeColor: "rgba(0, 206, 209, 1)",
      },
    };
  },
  props: {
    relationshipData: {
      type: Object,
      default: () => {},
    },
    singFlag: {
      type: Boolean,
      default: false,
    },
    value1: {
      type: Boolean,
      default: true,
    },
  },
  watch: {
    relationshipData: {
      immediate: true,
      deep: true,
      handler(val) {
        if (val?.tableList?.length > 0) {
          this.nodeList = val?.tableList.map((item) => {
            return {
              id: String(item?.id),
              color: item?.id == val?.node?.id ? "#3FD3A1" : "#3C91FF",
              width: 54,
              height: 54,
              borderColor: "transparent",
              borderWidth: 0,
              data: {
                myicon: "el-icon-notebook-2",
                text: item?.tableName,
                tableDesc: item?.tableDesc,
              },
            };
          });
          this.linesList = val?.edgeList.map((item1) => {
            return {
              from: String(item1?.fromTableId),
              to: String(item1?.toTableId),
              color: this.singFlag ? "#E5E5E5" : "#3C91FF",
              lineWidth: 2,
              edgeGroup: item1?.edgeGroup,
            };
          });
          this.$nextTick(() => {
            this.showGraph();
          });
        } else {
          this.nodeList = [];
          this.linesList = [];
          this.$nextTick(() => {
            this.showGraph();
          });
        }
      },
    },
  },
  mounted() {
    // this.showGraph();
  },
  methods: {
    showNodeTips(nodeObject, $event) {
      this.currentNode = nodeObject;
      const _base_position = this.$refs.myPage.getBoundingClientRect();
      this.isShowNodeTipsPanel = true;
      this.nodeMenuPanelPosition.x = $event.clientX - _base_position.x + 10;
      this.nodeMenuPanelPosition.y = $event.clientY - _base_position.y + 10;
    },
    hideNodeTips(nodeObject, $event) {
      this.isShowNodeTipsPanel = false;
    },
    getAllNodeLine(data) {
      // console.log("点击拿到的类别", data, "this.linesList", this.linesList);
      if (data) {
        let arrIds = [];
        // this.linesList = this.linesList.filter((item) => item.id);
        this.linesList.forEach((item) => {
          if (item?.edgeGroup == data) {
            arrIds.push(item.from, item.to);
            item.color = "#3FD3A1";
          } else {
            item.color = this.singFlag ? "#E5E5E5" : "#3C91FF";
          }
        });
        arrIds = [...new Set(arrIds)];
        this.nodeList.forEach((item) => {
          if (arrIds.includes(item?.id)) {
            item.color = "#3FD3A1";
          } else {
            item.color = "#3C91FF";
          }
        });
        this.showGraph();
      }
    },
    async showGraph() {
      let __graph_json_data = {};
      __graph_json_data = {
        rootId: "a",
        nodes: this.nodeList,
        lines: this.linesList,
      };

      // __graph_json_data?.nodes?.forEach((node) => {
      //   // 随机设置节点宽高
      //   // node.width = 40 + Math.floor(Math.random() * 200)
      //   // node.height = 40
      // });
      let lineIndex = 0;
      __graph_json_data?.lines?.forEach((line) => {
        // console.log("line", line);
        line.data = {
          // 自定义属性放在data中
          points: [], // 准备一个属性,待会儿接收从dagre生成的线条点
          edgeGroup: line?.edgeGroup, //存放类别
        };
        line.lineDirection = "v";
        line.id = "L" + lineIndex++; // id 必须是字符串
        // line.text = "";
      });
      const g = new dagre.graphlib.Graph();
      g.setGraph({
        // ranker: "longest-path",
        ranker: "tight-tree",
        nodesep: 30,
        ranksep: 200,
        // rankdir: 'TB'
        // rankdir:指定节点排名的方向,可以是从上到下(TB)、从下到上(BT)、从左到右(LR)或从右到左(RL)。
        // align:指定排名节点的对齐方式,可以是左上(UL)、右上(UR)、左下(DL)或右下(DR)。
        // nodesep:指定布局中水平分隔节点的像素数。
        // edgesep:指定布局中水平分隔边的像素数。
        // ranksep:指定布局中每个排名之间的像素数。
        // marginx:指定图形左右两侧用作边缘的像素数。
        // marginy:指定图形上下两侧用作边缘的像素数。
        // acyclicer:如果设置为greedy,则使用贪心启发式算法找到图的反馈弧集,即一组可以移除的边,使得图变为无环。
        // ranker:指定用于为输入图中的每个节点分配排名的算法类型,可以是network-simplex、tight-tree或longest-path
      });
      // g.setDefaultEdgeLabel(function () {
      //   return {};
      // });
      const graphInstance = this.$refs.graphRef?.getInstance();
      await graphInstance?.setJsonData(__graph_json_data);
      graphInstance?.getNodes()?.forEach((node) => {
        node.width = node?.el?.offsetWidth;
        node.height = node?.el?.offsetHeight;
        // console.log('offsetHeight', node);
        g.setNode(node?.id, node);
      });
      graphInstance?.getLinks()?.forEach((link) => {
        link.relations.forEach((line) => {
          g.setEdge(link.fromNode.id, link.toNode.id, {
            id: line.id, // 设置id,到会儿通过id找到jsonData中的line,设置line.data.points
            // weight: 0  // 这是一个非常重要的参数,尝试设置为0或者1或者中间值查看效果
          });
        });
      });
      dagre.layout(g);
      graphInstance?.getNodes()?.forEach((node) => {
        // 由于dagre生成的坐标是指向节点中心的,所以要根据节点宽高做偏移
        node.x = node.x - node.el.offsetWidth / 2 - 5;
        node.y = node.y - node.el.offsetHeight / 2 - 5;
      });
      g.edges().forEach((e) => {
        const edge = g.edge(e); // 读取dagre生成的连线信息,写入relation-graph的线条属性中
        // console.log("22222222222222222", edge);
        const line = this.getLineById(graphInstance, edge.id);
        const link = this.getLinkByLineId(graphInstance, edge.id);
        line.data.points = edge.points;
        line.data.startPointOffset = {
          // 为了支持节点移动时线条跟着动
          x: edge.points[0].x - link.fromNode.x,
          y: edge.points[0].y - link.fromNode.y,
        };
        line.data.endPointOffset = {
          // 为了支持节点移动时线条跟着动
          x: edge.points[edge.points.length - 1].x - link.toNode.x,
          y: edge.points[edge.points.length - 1].y - link.toNode.y,
        };
      });
      console.log(graphInstance, "graphInstance------");

      await graphInstance?.refresh();
      await graphInstance?.moveToCenter();
      await graphInstance?.zoomToFit();
    },
    getLineById(graphInstance, lineId) {
      for (const link of graphInstance.getLinks()) {
        for (const line of link.relations) {
          if (line.id === lineId) {
            return line;
          }
        }
      }
    },
    getLinkByLineId(graphInstance, lineId) {
      for (const link of graphInstance.getLinks()) {
        for (const line of link.relations) {
          if (line.id === lineId) {
            return link;
          }
        }
      }
    },
    onNodeClick(nodeObject, $event) {
      // console.log("onNodeClick:", nodeObject, "this.linesList", this.linesList);
      this.isShowNodeTipsPanel = false;
      this.edgeGroupSign = null;
      const flag = this.linesList.some((item) => {
        if (item?.to == nodeObject?.id || item?.from == nodeObject?.id) {
          this.edgeGroupSign = item?.edgeGroup;
          return true;
        }
      });
      // console.log(`flag:`, flag, "this.edgeGroupSign", this.edgeGroupSign);
      if (!flag) {
        this.edgeGroupSign = null;
        this.nodeList.forEach((item) => {
          if (item?.id == nodeObject?.id) {
            item.color = "#3FD3A1";
          } else {
            item.color = "#3C91FF";
          }
        });
        this.linesList.forEach((item) => {
          item.color = this.singFlag ? "#E5E5E5" : "#3C91FF";
        });

        this.showGraph();
      } else {
        this.getAllNodeLine(this.edgeGroupSign);
      }
    },
    onLineClick(lineObject) {
      this.isShowNodeTipsPanel = false;
      this.getAllNodeLine(lineObject?.data?.edgeGroup);
    },
  },
};
</script>

<style lang="scss"></style>

<style lang="scss" scoped>
.c-my-panel {
  width: 400px;
  position: absolute;
  left: 10px;
  top: 10px;
  border-radius: 10px;
  z-index: 800;
  background-color: #efefef;
  border: #eeeeee solid 1px;
  padding: 10px;
  .c-option-name {
    color: #666666;
    font-size: 14px;
    line-height: 40px;
    padding-left: 10px;
    padding-right: 10px;
  }
  .c-my-options {
    text-align: center;
    .c-my-option-item {
      text-align: left;
      color: #1da9f5;
      cursor: pointer;
      border-radius: 5px;
      padding-left: 10px;
      margin-top: 5px;
      line-height: 25px;
      &:hover {
        background-color: rgba(29, 169, 245, 0.2);
      }
    }
  }
}
.c-my-rg-node {
  height: 54px;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  place-items: center;
  justify-content: center;
  color: #d8d8d8;
}
.c-node-menu-item {
  height: 30px;
  display: flex;
  align-items: center;
  padding-left: 10px;
  font-size: 14px;
}
::v-deep .rel-node {
  height: 54px !important;
  width: 54px !important;
}
/* ::v-deep .rel-map-canvas {
  margin-left: 50px !important;
  margin-top: 30px !important;
} */
</style>

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐