G6: 求一个自定义节点和自定义边的思路

Created on 6 Aug 2019  ·  11Comments  ·  Source: antvis/G6

Screen Shot 2019-08-06 at 16 41 12

如上图,要用 G6 做出这个关系图,现在需要满足三个要求:

  1. 把每个节点定义成一个列表, 并且样式要满足上图要求;
  2. 列表的每一项都可以响应点击事件, 回调里要返回该项的信息;
  3. 节点之间的边连接的时候可以指定连接列表中的某一项, 如上图中的 7 auth time 要连接 9 time_type

我查了一下 文档,这种情况自定义节点最好应该是 新增一种 shape dom, 然后自己拼 html 形成一个列表,并加上 css 自定义样式。于是我结合 treeGraph 得到下面这种形状

Screen Shot 2019-08-06 at 17 55 55

这满足了要求 1, 但是 2 和 3 无法满足。

对于要求2, 自定义 html 的节点如何自定义点击事件具体到列表的每一项?

对于要求 3,我看了文档可以自定义锚点位置,
但是我这个节点是 html 制成的,如何指定连接到列表某一项的位置?

Most helpful comment

我封装了一个组件

<template>
  <div class="table-relation-graph">
    <div ref="mountNode"></div>
    <m:jiangdi.conextmenu ref="menu">
      <template slot-scope="child">
        <ul v-if="getMenuData(child.data).info">
          <li
            :key="index"
            v-for="(item, index) in getMenuData(child.data).info.menu"
            @click="menuClick(getMenuData(child.data).info, item.type)"
          >
            {{ item.name }}
          </li>
        </ul>
      </template>
    </m:jiangdi.conextmenu>
  </div>
</template>

<script>
import G6 from "@antv/g6";
import Hierarchy from "@antv/hierarchy";
// import contextMenu from "./menu.vue";

const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
  return [
    ["M", x, y],
    ["a", r, r, 0, 1, 0, r * 2, 0],
    ["a", r, r, 0, 1, 0, -r * 2, 0],
    ["M", x + 2, y],
    ["L", x + 2 * r - 2, y]
  ];
};
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
  return [
    ["M", x, y],
    ["a", r, r, 0, 1, 0, r * 2, 0],
    ["a", r, r, 0, 1, 0, -r * 2, 0],
    ["M", x + 2, y],
    ["L", x + 2 * r - 2, y],
    ["M", x + r, y - r + 2],
    ["L", x + r, y + r - 2]
  ];
};
const textCut = (text, n) => {
  let result = text.slice(0, n);
  if (text.length > n) {
    result += "...";
  }
  return result;
};

export default {
  props: {
    nodeData: {
      type: Object,
      default: () => {}
    },
    edgeData: {
      type: Array,
      default: () => []
    },
    width: {
      type: Number
    },
    height: {
      type: Number
    }
  },
  data() {
    return {
      graph: {}
    };
  },
  mounted() {
    this.registerNode();
    this.registerBehavior();
    this.initGraph();
  },
  methods: {
    registerNode() {
      G6.registerNode("list-node", {
        draw: (cfg, group) => {
          let tableName = cfg.data.name;
          let tableId = cfg.data.id;
          let fields = cfg.data.fields;
          const rectWidth = 220;
          const rectHeight = 30;
          if (!tableName) {
            throw new Error("name 字段为必填项");
          }
          let fieldNameFillColor = "rgb(248, 248, 250)";
          const tableNameFillColor = "rgb(84,96,115)";

          let fontColor = "#707070";

          group.addShape("rect", {
            attrs: {
              x: 0,
              y: 0,
              tableId: tableId,
              width: rectWidth,
              height: rectHeight,
              radius: [2, 2],
              fill: tableNameFillColor,
              type: "tableName"
            }
          });

          group.addShape("text", {
            attrs: {
              x: 10,
              y: rectHeight - 4,
              tableId: tableId,
              fontFamily: "PingFang SC",
              text: textCut(tableName, 20),
              fontSize: 18,
              fill: "#fff",
              type: "tableName"
            }
          });

          group.addShape("marker", {
            attrs: {
              x: rectWidth - 12,
              y: rectHeight / 2,
              r: 7,
              symbol: "triangle-down",
              fill: "#fff",
              tableId: cfg.data.id,
              type: "field-expand-icon"
            },
            className: `field-expand`
          });

          if (fields && fields.length) {
            fields.forEach((item, index) => {
              const hasChildren = this.edgeData.find(i => {
                return i.source.id === tableId && i.source.field === item.id;
              });

              group.addShape("rect", {
                attrs: {
                  x: 0,
                  y: (index + 1) * rectHeight,
                  width: rectWidth,
                  height: rectHeight,
                  stroke: "rgb(240, 240, 241)",
                  radius: [2, 2],
                  fill: fieldNameFillColor,
                  tableId: tableId,
                  fieldId: item.id,
                  fieldIndex: index,
                  type: "fieldName"
                }
              });

              const hasType4 = item.info.menu.find(i => i.type === 4)

              group.addShape("text", {
                attrs: {
                  x: 10,
                  y: (index + 2) * rectHeight - 4,
                  fontFamily: "PingFang SC",
                  text: textCut(item.name, 15),
                  fontSize: 18,
                  fill: hasType4 ? "rgb(102, 177, 255)" : fontColor,
                  tableId: tableId,
                  fieldId: item.id,
                  fieldIndex: index,
                  type: "fieldName"
                }
              });

              group.addShape("image", {
                attrs: {
                  x: rectWidth - 45,
                  y: rectHeight / 2 + (index + 1) * rectHeight - 13,
                  width: 24,
                  height: 24,
                  tableId: tableId,
                  fieldId: item.id,
                  type: "menu",
                  img: "https://si.geilicdn.com/img-0a220000016c85e2ef940a21924a-unadjust_200_200.png"
                }
              });

              if (!hasChildren) {
                return;
              }

              group.addShape("marker", {
                attrs: {
                  x: rectWidth - 12,
                  y: rectHeight / 2 + (index + 1) * rectHeight,
                  r: 6,
                  symbol: COLLAPSE_ICON,
                  stroke: "rgb(153,173,206)",
                  lineWidth: 2,
                  tableId: cfg.data.id,
                  fieldId: item.id,
                  fieldIndex: index,
                  type: "expand-icon"
                },
                className: `expand-icon-${index}`
              });
            });
          }
          return group;
        },
        getAnchorPoints(cfg) {
          const fields = cfg.data.fields;
          const singleLength = 1 / (fields.length + 1);
          return [
            [1, singleLength / 2],
            ...fields.map((item, index) => {
              return [1, singleLength / 2 + singleLength * (index + 1)];
            }),
            [0, singleLength / 2],
            ...fields.map((item, index) => {
              return [0, singleLength / 2 + singleLength * (index + 1)];
            })
          ];
        }
      });
    },
    registerBehavior() {},
    initGraph() {
      if (this.graph && this.graph.destroy) {
        this.graph.destroy();
      }

      // 没有边的数据,或节点的数据则中断
      if (!this.nodeData || !this.nodeData.id) {
        return;
      }
      if (!this.edgeData) {
        return;
      }

      const noChildren = (!this.edgeData) || (this.edgeData.length < 2)
      this.graph = new G6.TreeGraph({
        container: this.$refs.mountNode,
        width: noChildren ? 500 : this.width,
        height: this.height,
        modes: {
          default: []
        },
        defaultEdge: {
          shape: "cubic-horizontal",
          style: {
            stroke: "rgb(181,181,193)",
            lineWidth: 2
          }
        },
        defaultNode: {
          shape: "list-node"
        },
        nodeStyle: {
          default: {}
        },
        layout: function layout(data) {
          return Hierarchy.mindmap(data, {
            direction: "H",
            getHeight() {
              return 150;
            },
            getWidth() {
              return 150;
            },
            getVGap() {
              return 10;
            },
            getHGap() {
              return 100;
            },
            getSide() {
              return "right";
            }
          });
        }
      });
      this.graph.data(this.nodeData);
      this.graph.render();

      this.setEdgesAnchor();
      this.setEvents();

      this.graph.refresh();
      this.graph.fitView();
    },
    adjustTargetAnchorWhenFieldCollapse(node) {
      const edges = node.getInEdges();
      edges.forEach(i => {
        i.get("model").targetAnchor = node.getModel().data.fields.length + 1;
      });
    },
    setEdgesAnchor() {
      this.graph.getEdges().forEach(edge => {
        const sourceItem = edge.getSource();
        const targetItem = edge.getTarget();

        const thisEdge = this.edgeData.find(item => {
          return item.source.id === edge.getModel().source && item.target.id === edge.getModel().target;
        });
        if (thisEdge.related === false) {
          edge.get("model").style = {
            lineDash: [10, 10],
            stroke: "rgb(181,181,193)",
            lineWidth: 2
          };
        }
        const source = thisEdge.source;
        const target = thisEdge.target;
        let sourceAnchorIndex = 0;
        G6.Util.traverseTree(this.nodeData, item => {
          if (source.id === item.id) {
            sourceAnchorIndex = item.fields.findIndex(i => i.id === source.field) + 1;
          }
        });

        let targetAnchorIndex = 0;

        G6.Util.traverseTree(this.nodeData, item => {
          if (target.id === item.id) {
            targetAnchorIndex = item.fields.length + 2 + item.fields.findIndex(i => i.id === target.field);
          }
        });

        edge.get("model").sourceAnchor = sourceAnchorIndex;
        edge.get("model").targetAnchor = targetAnchorIndex;
      });
    },
    setEvents() {
      this.graph.on("node:click", ev => {
        let _this = this;
        const target = ev.target;
        function collapse(item, fieldId = null) {
          let children = item.get("model").children;
          if (fieldId) {
            _this.graph.setItemState(item, String(fieldId), true);
            let edge = _this.edgeData.find(i => {
              return i.source.id === item.get("model").id && i.source.field === fieldId;
            });

            if (!edge) {
              return;
            }
            children = item.get("model").children.filter(i => {
              return i.id === edge.target.id;
            });
          }

          G6.Util.each(children, child => {
            const node = _this.graph.findById(child.id);
            collapse(node);
            _this.graph.hideItem(node);
          });
        }

        function expand(item, fieldId) {
          let children = item.get("model").children;
          if (fieldId) {
            _this.graph.setItemState(item, String(fieldId), false);
            let edge = _this.edgeData.find(i => {
              return i.source.id === item.get("model").id && i.source.field === fieldId;
            });

            if (!edge) {
              return;
            }

            children = item.get("model").children.filter(i => {
              return i.id === edge.target.id;
            });
          }

          _this.graph.showItem(item);
          G6.Util.each(children, child => {
            const node = _this.graph.findById(child.id);
            expand(node);
          });
        }

        const triggerExpandChildren =
          (target.attr && target.attr("type") === "expand-icon") || target.attr("type") === "fieldName";
        const triggerExpandFields =
          (target.attr && target.attr("type") === "field-expand-icon") || target.attr("type") === "tableName";
        const triggerShowMenu = target.attr && target.attr("type") === "menu";
        if (triggerExpandChildren) {
          const fieldId = target.attr("fieldId");
          const fieldIndex = target.attr("fieldIndex");
          const fieldIdStr = String(fieldId);
          const icon = ev.item.get("group").findByClassName(`expand-icon-${fieldIndex}`);
          const collapsed = !ev.item.hasState(fieldIdStr);

          if (collapsed) {
            icon && icon.attr("symbol", EXPAND_ICON);
            collapse(ev.item, fieldId);
          } else {
            icon && icon.attr("symbol", COLLAPSE_ICON);
            expand(ev.item, fieldId);
          }
        } else if (triggerExpandFields) {
          let item = ev.item;
          const tableCollapsed = !item.hasState("table-collapsed");
          this.graph.setItemState(item, "table-collapsed", tableCollapsed);
          const group = item.get("group");
          const icon = group.findByClassName(`field-expand`);

          const hideFields = () => {
            group.get("children").forEach(i => {
              const type = i.attr("type");
              if (type === "tableName") {
                // do nothing
              } else if (type === "field-expand-icon") {
                // do nothing
              } else {
                i.hide();
              }
            });
          };

          const showFields = () => {
            group.get("children").forEach(i => {
              const type = i.attr("type");
              if (type === "tableName") {
                // do nothing
              } else if (type === "field-expand-icon") {
                // do nothing
              } else {
                i.show();
              }
            });
          };

          if (tableCollapsed) {
            icon && icon.attr("symbol", "triangle");
            collapse(item);
            hideFields();

            // hack: G6 的 bug, 当没有子节点时无法隐藏,要手动 hide 一下再 show 一下。。。
            this.graph.hideItem(item);
            this.graph.showItem(item);
            this.adjustTargetAnchorWhenFieldCollapse(item);
          } else {
            icon && icon.attr("symbol", "triangle-down");
            expand(item);
            showFields();
            this.setEdgesAnchor();
          }

          this.graph.refreshPositions();
        } else if (triggerShowMenu) {
          if (this.$refs.menu.showContextMenu) {
            this.closeMenu();
          } else {
            this.$refs.menu.open(ev.event, {
              tableId: target.attr("tableId"),
              fieldId: target.attr("fieldId")
            });
          }
        }
      });

      this.graph.on("canvas:click", () => {
        this.closeMenu();
      });
    },
    closeMenu() {
      this.$refs.menu.close();
    },
    getMenuData({ tableId, fieldId }) {
      let data = {};

      if (!this.nodeData || !this.nodeData.id) {
        return data;
      }

      G6.Util.traverseTree(this.nodeData, item => {
        if (item.id === tableId) {
          data = item.fields.find(i => i.id === fieldId);
        }
      });

      return data;
    },
    menuClick(info, type) {
      this.closeMenu();
      this.$emit("menuClick", {
        info,
        type
      });
    }
  },
  components: {
    // contextMenu
  },
  watch: {
    nodeData: {
      handler: function() {
        this.initGraph();
      },
      deep: true
    },
    edgeData: {
      handler: function() {
        this.initGraph();
      },
      deep: true
    }
  }
};
</script>

<style lang="less">
.table-relation-graph {
  height: 100%;
  width: 100%;
}
</style>

使用方式如下:

<template>
  <div>
    <tableRelationGraph
      :nodeData="nodeData"
      :edgeData="edgeData"
      @menuClick="menuClick"
      :width="width"
      :height="height"
    ></tableRelationGraph>
  </div>
</template>

<script>
import tableRelationGraph from "./index.vue";
let nodeData = {
  name: "dw.dwd_trd_sub_ord",
  id: "57",
  type: false,
  root: true,
  fields: [
    {
      name: "seller_id",
      id: 215,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    },
    {
      name: "buyer_id",
      id: 216,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    },
    {
      name: "finish_time",
      id: 217,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    },
    {
      name: "refund_time",
      id: 218,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    }
  ],
  children: [
    {
      name: "dw.dim_buyer",
      id: "52_17",
      type: false,
      fields: [
        {
          name: "buyer_id",
          id: 205,
          info: {
            route: "17",
            tableId: 52,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "card_type",
          id: 206,
          info: {
            route: "17",
            tableId: 52,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_buyer_card",
          id: "54_17,15",
          type: false,
          fields: [
            {
              name: "card_type",
              id: 209,
              info: {
                route: "17,15",
                tableId: 54,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            },
            {
              name: "card_desc",
              id: 210,
              info: {
                route: "17,15",
                tableId: 54,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: []
        }
      ]
    },
    {
      name: "dw.dim_seller",
      id: "56_18",
      type: false,
      fields: [
        {
          name: "seller_id",
          id: 213,
          info: {
            route: "18",
            tableId: 56,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "auth_time",
          id: 214,
          info: {
            route: "18",
            tableId: 56,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_time",
          id: "55_18,16",
          type: false,
          fields: [
            {
              name: "time_type",
              id: 211,
              info: {
                route: "18,16",
                tableId: 55,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            },
            {
              name: "time_desc",
              id: 212,
              info: {
                route: "18,16",
                tableId: 55,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: [
            {
              name: "dw.dim_time_desc",
              id: "58_18,16,21",
              type: false,
              fields: [
                {
                  name: "time_desc_id",
                  id: 237,
                  info: {
                    route: "18,16,21",
                    tableId: 58,
                    modelId: 28,
                    menu: [
                      {
                        name: "新建原子指标",
                        type: 0
                      },
                      {
                        name: "新建统计粒度",
                        type: 1
                      },
                      {
                        name: "新建业务限定",
                        type: 2
                      },
                      {
                        name: "新建时间周期",
                        type: 3
                      }
                    ]
                  }
                }
              ],
              children: []
            }
          ]
        }
      ]
    },
    {
      name: "dw.dim_time",
      id: "55_19",
      type: false,
      fields: [
        {
          name: "time_type",
          id: 211,
          info: {
            route: "19",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "time_desc",
          id: 212,
          info: {
            route: "19",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_time_desc",
          id: "58_19,21",
          type: false,
          fields: [
            {
              name: "time_desc_id",
              id: 237,
              info: {
                route: "19,21",
                tableId: 58,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: []
        }
      ]
    },
    {
      name: "dw.dim_time",
      id: "55_20",
      type: false,
      fields: [
        {
          name: "time_type",
          id: 211,
          info: {
            route: "20",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "time_desc",
          id: 212,
          info: {
            route: "20",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_time_desc",
          id: "58_20,21",
          type: false,
          fields: [
            {
              name: "time_desc_id",
              id: 237,
              info: {
                route: "20,21",
                tableId: 58,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: []
        }
      ]
    }
  ]
};

let edgeData = [
  {
    source: {
      id: "57",
      field: 216
    },
    target: {
      id: "52_17",
      field: 205
    },
    related: true
  },
  {
    source: {
      id: "52_17",
      field: 206
    },
    target: {
      id: "54_17,15",
      field: 209
    },
    related: false
  },
  {
    source: {
      id: "57",
      field: 215
    },
    target: {
      id: "56_18",
      field: 213
    },
    related: false
  },
  {
    source: {
      id: "56_18",
      field: 214
    },
    target: {
      id: "55_18,16",
      field: 211
    },
    related: false
  },
  {
    source: {
      id: "55_18,16",
      field: 212
    },
    target: {
      id: "58_18,16,21",
      field: 237
    },
    related: false
  },
  {
    source: {
      id: "57",
      field: 217
    },
    target: {
      id: "55_19",
      field: 211
    },
    related: false
  },
  {
    source: {
      id: "55_19",
      field: 212
    },
    target: {
      id: "58_19,21",
      field: 237
    },
    related: false
  },
  {
    source: {
      id: "57",
      field: 218
    },
    target: {
      id: "55_20",
      field: 211
    },
    related: true
  },
  {
    source: {
      id: "55_20",
      field: 212
    },
    target: {
      id: "58_20,21",
      field: 237
    },
    related: true
  }
];
export default {
  data() {
    return {
      nodeData: {},
      edgeData: [],
      width: 1000,
      height: 500
    };
  },
  mounted() {
    setTimeout(() => {
      this.nodeData = nodeData;
      this.edgeData = edgeData;
    }, 1000);
  },
  methods: {
    menuClick(data) {
      console.log(data.info, data.type);
    }
  },
  components: {
    // tableRelationGraph
  }
};
</script>

<style scoped></style>

但是我这个包含了比较多的业务逻辑所以看起来比较乱 @LynnAlan

All 11 comments

new Graph的时候指定 renderer: 'svg' ,切换成使用SVG渲染呢?
使用使用Canvas渲染,可以考虑以下做法:
1、定义节点时候,每个节点中的每一行是一个rect;
2、点击获取节点部分,可参考这里
3、指定anchorPoints,用于指定连接位置。

@baizn 非常感谢~ 我就是用 svg 的,那个列表我是拼 html 的,用 rect 一个个拼也太麻烦了。。。

@baizn 你给的这个例子是用 canvas 的, 用 svg 渲染如何响应节点区域事件呢?

我完成了

Screen Shot 2019-08-09 at 18 45 43

另外吐槽一下, 3.0.4 API 不兼容 3.0.2, API 有破坏式更新时你们应该升大版本号

我完成了

有时间的话可以作为一个demo提交一个pr,有类似需求的同学就可以直接参考了。

好的

@dd1994 请教一下,你是如何实现的,自定义html节点,边无法显示,能给个demo参考下吗,非常感谢!

@dd1994 大佬 你好 你最后完成的作品,可以提供一个demo参考下吗?非常感谢。

我封装了一个组件

<template>
  <div class="table-relation-graph">
    <div ref="mountNode"></div>
    <m:jiangdi.conextmenu ref="menu">
      <template slot-scope="child">
        <ul v-if="getMenuData(child.data).info">
          <li
            :key="index"
            v-for="(item, index) in getMenuData(child.data).info.menu"
            @click="menuClick(getMenuData(child.data).info, item.type)"
          >
            {{ item.name }}
          </li>
        </ul>
      </template>
    </m:jiangdi.conextmenu>
  </div>
</template>

<script>
import G6 from "@antv/g6";
import Hierarchy from "@antv/hierarchy";
// import contextMenu from "./menu.vue";

const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
  return [
    ["M", x, y],
    ["a", r, r, 0, 1, 0, r * 2, 0],
    ["a", r, r, 0, 1, 0, -r * 2, 0],
    ["M", x + 2, y],
    ["L", x + 2 * r - 2, y]
  ];
};
const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
  return [
    ["M", x, y],
    ["a", r, r, 0, 1, 0, r * 2, 0],
    ["a", r, r, 0, 1, 0, -r * 2, 0],
    ["M", x + 2, y],
    ["L", x + 2 * r - 2, y],
    ["M", x + r, y - r + 2],
    ["L", x + r, y + r - 2]
  ];
};
const textCut = (text, n) => {
  let result = text.slice(0, n);
  if (text.length > n) {
    result += "...";
  }
  return result;
};

export default {
  props: {
    nodeData: {
      type: Object,
      default: () => {}
    },
    edgeData: {
      type: Array,
      default: () => []
    },
    width: {
      type: Number
    },
    height: {
      type: Number
    }
  },
  data() {
    return {
      graph: {}
    };
  },
  mounted() {
    this.registerNode();
    this.registerBehavior();
    this.initGraph();
  },
  methods: {
    registerNode() {
      G6.registerNode("list-node", {
        draw: (cfg, group) => {
          let tableName = cfg.data.name;
          let tableId = cfg.data.id;
          let fields = cfg.data.fields;
          const rectWidth = 220;
          const rectHeight = 30;
          if (!tableName) {
            throw new Error("name 字段为必填项");
          }
          let fieldNameFillColor = "rgb(248, 248, 250)";
          const tableNameFillColor = "rgb(84,96,115)";

          let fontColor = "#707070";

          group.addShape("rect", {
            attrs: {
              x: 0,
              y: 0,
              tableId: tableId,
              width: rectWidth,
              height: rectHeight,
              radius: [2, 2],
              fill: tableNameFillColor,
              type: "tableName"
            }
          });

          group.addShape("text", {
            attrs: {
              x: 10,
              y: rectHeight - 4,
              tableId: tableId,
              fontFamily: "PingFang SC",
              text: textCut(tableName, 20),
              fontSize: 18,
              fill: "#fff",
              type: "tableName"
            }
          });

          group.addShape("marker", {
            attrs: {
              x: rectWidth - 12,
              y: rectHeight / 2,
              r: 7,
              symbol: "triangle-down",
              fill: "#fff",
              tableId: cfg.data.id,
              type: "field-expand-icon"
            },
            className: `field-expand`
          });

          if (fields && fields.length) {
            fields.forEach((item, index) => {
              const hasChildren = this.edgeData.find(i => {
                return i.source.id === tableId && i.source.field === item.id;
              });

              group.addShape("rect", {
                attrs: {
                  x: 0,
                  y: (index + 1) * rectHeight,
                  width: rectWidth,
                  height: rectHeight,
                  stroke: "rgb(240, 240, 241)",
                  radius: [2, 2],
                  fill: fieldNameFillColor,
                  tableId: tableId,
                  fieldId: item.id,
                  fieldIndex: index,
                  type: "fieldName"
                }
              });

              const hasType4 = item.info.menu.find(i => i.type === 4)

              group.addShape("text", {
                attrs: {
                  x: 10,
                  y: (index + 2) * rectHeight - 4,
                  fontFamily: "PingFang SC",
                  text: textCut(item.name, 15),
                  fontSize: 18,
                  fill: hasType4 ? "rgb(102, 177, 255)" : fontColor,
                  tableId: tableId,
                  fieldId: item.id,
                  fieldIndex: index,
                  type: "fieldName"
                }
              });

              group.addShape("image", {
                attrs: {
                  x: rectWidth - 45,
                  y: rectHeight / 2 + (index + 1) * rectHeight - 13,
                  width: 24,
                  height: 24,
                  tableId: tableId,
                  fieldId: item.id,
                  type: "menu",
                  img: "https://si.geilicdn.com/img-0a220000016c85e2ef940a21924a-unadjust_200_200.png"
                }
              });

              if (!hasChildren) {
                return;
              }

              group.addShape("marker", {
                attrs: {
                  x: rectWidth - 12,
                  y: rectHeight / 2 + (index + 1) * rectHeight,
                  r: 6,
                  symbol: COLLAPSE_ICON,
                  stroke: "rgb(153,173,206)",
                  lineWidth: 2,
                  tableId: cfg.data.id,
                  fieldId: item.id,
                  fieldIndex: index,
                  type: "expand-icon"
                },
                className: `expand-icon-${index}`
              });
            });
          }
          return group;
        },
        getAnchorPoints(cfg) {
          const fields = cfg.data.fields;
          const singleLength = 1 / (fields.length + 1);
          return [
            [1, singleLength / 2],
            ...fields.map((item, index) => {
              return [1, singleLength / 2 + singleLength * (index + 1)];
            }),
            [0, singleLength / 2],
            ...fields.map((item, index) => {
              return [0, singleLength / 2 + singleLength * (index + 1)];
            })
          ];
        }
      });
    },
    registerBehavior() {},
    initGraph() {
      if (this.graph && this.graph.destroy) {
        this.graph.destroy();
      }

      // 没有边的数据,或节点的数据则中断
      if (!this.nodeData || !this.nodeData.id) {
        return;
      }
      if (!this.edgeData) {
        return;
      }

      const noChildren = (!this.edgeData) || (this.edgeData.length < 2)
      this.graph = new G6.TreeGraph({
        container: this.$refs.mountNode,
        width: noChildren ? 500 : this.width,
        height: this.height,
        modes: {
          default: []
        },
        defaultEdge: {
          shape: "cubic-horizontal",
          style: {
            stroke: "rgb(181,181,193)",
            lineWidth: 2
          }
        },
        defaultNode: {
          shape: "list-node"
        },
        nodeStyle: {
          default: {}
        },
        layout: function layout(data) {
          return Hierarchy.mindmap(data, {
            direction: "H",
            getHeight() {
              return 150;
            },
            getWidth() {
              return 150;
            },
            getVGap() {
              return 10;
            },
            getHGap() {
              return 100;
            },
            getSide() {
              return "right";
            }
          });
        }
      });
      this.graph.data(this.nodeData);
      this.graph.render();

      this.setEdgesAnchor();
      this.setEvents();

      this.graph.refresh();
      this.graph.fitView();
    },
    adjustTargetAnchorWhenFieldCollapse(node) {
      const edges = node.getInEdges();
      edges.forEach(i => {
        i.get("model").targetAnchor = node.getModel().data.fields.length + 1;
      });
    },
    setEdgesAnchor() {
      this.graph.getEdges().forEach(edge => {
        const sourceItem = edge.getSource();
        const targetItem = edge.getTarget();

        const thisEdge = this.edgeData.find(item => {
          return item.source.id === edge.getModel().source && item.target.id === edge.getModel().target;
        });
        if (thisEdge.related === false) {
          edge.get("model").style = {
            lineDash: [10, 10],
            stroke: "rgb(181,181,193)",
            lineWidth: 2
          };
        }
        const source = thisEdge.source;
        const target = thisEdge.target;
        let sourceAnchorIndex = 0;
        G6.Util.traverseTree(this.nodeData, item => {
          if (source.id === item.id) {
            sourceAnchorIndex = item.fields.findIndex(i => i.id === source.field) + 1;
          }
        });

        let targetAnchorIndex = 0;

        G6.Util.traverseTree(this.nodeData, item => {
          if (target.id === item.id) {
            targetAnchorIndex = item.fields.length + 2 + item.fields.findIndex(i => i.id === target.field);
          }
        });

        edge.get("model").sourceAnchor = sourceAnchorIndex;
        edge.get("model").targetAnchor = targetAnchorIndex;
      });
    },
    setEvents() {
      this.graph.on("node:click", ev => {
        let _this = this;
        const target = ev.target;
        function collapse(item, fieldId = null) {
          let children = item.get("model").children;
          if (fieldId) {
            _this.graph.setItemState(item, String(fieldId), true);
            let edge = _this.edgeData.find(i => {
              return i.source.id === item.get("model").id && i.source.field === fieldId;
            });

            if (!edge) {
              return;
            }
            children = item.get("model").children.filter(i => {
              return i.id === edge.target.id;
            });
          }

          G6.Util.each(children, child => {
            const node = _this.graph.findById(child.id);
            collapse(node);
            _this.graph.hideItem(node);
          });
        }

        function expand(item, fieldId) {
          let children = item.get("model").children;
          if (fieldId) {
            _this.graph.setItemState(item, String(fieldId), false);
            let edge = _this.edgeData.find(i => {
              return i.source.id === item.get("model").id && i.source.field === fieldId;
            });

            if (!edge) {
              return;
            }

            children = item.get("model").children.filter(i => {
              return i.id === edge.target.id;
            });
          }

          _this.graph.showItem(item);
          G6.Util.each(children, child => {
            const node = _this.graph.findById(child.id);
            expand(node);
          });
        }

        const triggerExpandChildren =
          (target.attr && target.attr("type") === "expand-icon") || target.attr("type") === "fieldName";
        const triggerExpandFields =
          (target.attr && target.attr("type") === "field-expand-icon") || target.attr("type") === "tableName";
        const triggerShowMenu = target.attr && target.attr("type") === "menu";
        if (triggerExpandChildren) {
          const fieldId = target.attr("fieldId");
          const fieldIndex = target.attr("fieldIndex");
          const fieldIdStr = String(fieldId);
          const icon = ev.item.get("group").findByClassName(`expand-icon-${fieldIndex}`);
          const collapsed = !ev.item.hasState(fieldIdStr);

          if (collapsed) {
            icon && icon.attr("symbol", EXPAND_ICON);
            collapse(ev.item, fieldId);
          } else {
            icon && icon.attr("symbol", COLLAPSE_ICON);
            expand(ev.item, fieldId);
          }
        } else if (triggerExpandFields) {
          let item = ev.item;
          const tableCollapsed = !item.hasState("table-collapsed");
          this.graph.setItemState(item, "table-collapsed", tableCollapsed);
          const group = item.get("group");
          const icon = group.findByClassName(`field-expand`);

          const hideFields = () => {
            group.get("children").forEach(i => {
              const type = i.attr("type");
              if (type === "tableName") {
                // do nothing
              } else if (type === "field-expand-icon") {
                // do nothing
              } else {
                i.hide();
              }
            });
          };

          const showFields = () => {
            group.get("children").forEach(i => {
              const type = i.attr("type");
              if (type === "tableName") {
                // do nothing
              } else if (type === "field-expand-icon") {
                // do nothing
              } else {
                i.show();
              }
            });
          };

          if (tableCollapsed) {
            icon && icon.attr("symbol", "triangle");
            collapse(item);
            hideFields();

            // hack: G6 的 bug, 当没有子节点时无法隐藏,要手动 hide 一下再 show 一下。。。
            this.graph.hideItem(item);
            this.graph.showItem(item);
            this.adjustTargetAnchorWhenFieldCollapse(item);
          } else {
            icon && icon.attr("symbol", "triangle-down");
            expand(item);
            showFields();
            this.setEdgesAnchor();
          }

          this.graph.refreshPositions();
        } else if (triggerShowMenu) {
          if (this.$refs.menu.showContextMenu) {
            this.closeMenu();
          } else {
            this.$refs.menu.open(ev.event, {
              tableId: target.attr("tableId"),
              fieldId: target.attr("fieldId")
            });
          }
        }
      });

      this.graph.on("canvas:click", () => {
        this.closeMenu();
      });
    },
    closeMenu() {
      this.$refs.menu.close();
    },
    getMenuData({ tableId, fieldId }) {
      let data = {};

      if (!this.nodeData || !this.nodeData.id) {
        return data;
      }

      G6.Util.traverseTree(this.nodeData, item => {
        if (item.id === tableId) {
          data = item.fields.find(i => i.id === fieldId);
        }
      });

      return data;
    },
    menuClick(info, type) {
      this.closeMenu();
      this.$emit("menuClick", {
        info,
        type
      });
    }
  },
  components: {
    // contextMenu
  },
  watch: {
    nodeData: {
      handler: function() {
        this.initGraph();
      },
      deep: true
    },
    edgeData: {
      handler: function() {
        this.initGraph();
      },
      deep: true
    }
  }
};
</script>

<style lang="less">
.table-relation-graph {
  height: 100%;
  width: 100%;
}
</style>

使用方式如下:

<template>
  <div>
    <tableRelationGraph
      :nodeData="nodeData"
      :edgeData="edgeData"
      @menuClick="menuClick"
      :width="width"
      :height="height"
    ></tableRelationGraph>
  </div>
</template>

<script>
import tableRelationGraph from "./index.vue";
let nodeData = {
  name: "dw.dwd_trd_sub_ord",
  id: "57",
  type: false,
  root: true,
  fields: [
    {
      name: "seller_id",
      id: 215,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    },
    {
      name: "buyer_id",
      id: 216,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    },
    {
      name: "finish_time",
      id: 217,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    },
    {
      name: "refund_time",
      id: 218,
      info: {
        route: "",
        tableId: 57,
        modelId: 28,
        menu: [
          {
            name: "新建原子指标",
            type: 0
          },
          {
            name: "新建统计粒度",
            type: 1
          },
          {
            name: "新建业务限定",
            type: 2
          },
          {
            name: "新建时间周期",
            type: 3
          }
        ]
      }
    }
  ],
  children: [
    {
      name: "dw.dim_buyer",
      id: "52_17",
      type: false,
      fields: [
        {
          name: "buyer_id",
          id: 205,
          info: {
            route: "17",
            tableId: 52,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "card_type",
          id: 206,
          info: {
            route: "17",
            tableId: 52,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_buyer_card",
          id: "54_17,15",
          type: false,
          fields: [
            {
              name: "card_type",
              id: 209,
              info: {
                route: "17,15",
                tableId: 54,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            },
            {
              name: "card_desc",
              id: 210,
              info: {
                route: "17,15",
                tableId: 54,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: []
        }
      ]
    },
    {
      name: "dw.dim_seller",
      id: "56_18",
      type: false,
      fields: [
        {
          name: "seller_id",
          id: 213,
          info: {
            route: "18",
            tableId: 56,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "auth_time",
          id: 214,
          info: {
            route: "18",
            tableId: 56,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_time",
          id: "55_18,16",
          type: false,
          fields: [
            {
              name: "time_type",
              id: 211,
              info: {
                route: "18,16",
                tableId: 55,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            },
            {
              name: "time_desc",
              id: 212,
              info: {
                route: "18,16",
                tableId: 55,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: [
            {
              name: "dw.dim_time_desc",
              id: "58_18,16,21",
              type: false,
              fields: [
                {
                  name: "time_desc_id",
                  id: 237,
                  info: {
                    route: "18,16,21",
                    tableId: 58,
                    modelId: 28,
                    menu: [
                      {
                        name: "新建原子指标",
                        type: 0
                      },
                      {
                        name: "新建统计粒度",
                        type: 1
                      },
                      {
                        name: "新建业务限定",
                        type: 2
                      },
                      {
                        name: "新建时间周期",
                        type: 3
                      }
                    ]
                  }
                }
              ],
              children: []
            }
          ]
        }
      ]
    },
    {
      name: "dw.dim_time",
      id: "55_19",
      type: false,
      fields: [
        {
          name: "time_type",
          id: 211,
          info: {
            route: "19",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "time_desc",
          id: 212,
          info: {
            route: "19",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_time_desc",
          id: "58_19,21",
          type: false,
          fields: [
            {
              name: "time_desc_id",
              id: 237,
              info: {
                route: "19,21",
                tableId: 58,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: []
        }
      ]
    },
    {
      name: "dw.dim_time",
      id: "55_20",
      type: false,
      fields: [
        {
          name: "time_type",
          id: 211,
          info: {
            route: "20",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        },
        {
          name: "time_desc",
          id: 212,
          info: {
            route: "20",
            tableId: 55,
            modelId: 28,
            menu: [
              {
                name: "新建原子指标",
                type: 0
              },
              {
                name: "新建统计粒度",
                type: 1
              },
              {
                name: "新建业务限定",
                type: 2
              },
              {
                name: "新建时间周期",
                type: 3
              }
            ]
          }
        }
      ],
      children: [
        {
          name: "dw.dim_time_desc",
          id: "58_20,21",
          type: false,
          fields: [
            {
              name: "time_desc_id",
              id: 237,
              info: {
                route: "20,21",
                tableId: 58,
                modelId: 28,
                menu: [
                  {
                    name: "新建原子指标",
                    type: 0
                  },
                  {
                    name: "新建统计粒度",
                    type: 1
                  },
                  {
                    name: "新建业务限定",
                    type: 2
                  },
                  {
                    name: "新建时间周期",
                    type: 3
                  }
                ]
              }
            }
          ],
          children: []
        }
      ]
    }
  ]
};

let edgeData = [
  {
    source: {
      id: "57",
      field: 216
    },
    target: {
      id: "52_17",
      field: 205
    },
    related: true
  },
  {
    source: {
      id: "52_17",
      field: 206
    },
    target: {
      id: "54_17,15",
      field: 209
    },
    related: false
  },
  {
    source: {
      id: "57",
      field: 215
    },
    target: {
      id: "56_18",
      field: 213
    },
    related: false
  },
  {
    source: {
      id: "56_18",
      field: 214
    },
    target: {
      id: "55_18,16",
      field: 211
    },
    related: false
  },
  {
    source: {
      id: "55_18,16",
      field: 212
    },
    target: {
      id: "58_18,16,21",
      field: 237
    },
    related: false
  },
  {
    source: {
      id: "57",
      field: 217
    },
    target: {
      id: "55_19",
      field: 211
    },
    related: false
  },
  {
    source: {
      id: "55_19",
      field: 212
    },
    target: {
      id: "58_19,21",
      field: 237
    },
    related: false
  },
  {
    source: {
      id: "57",
      field: 218
    },
    target: {
      id: "55_20",
      field: 211
    },
    related: true
  },
  {
    source: {
      id: "55_20",
      field: 212
    },
    target: {
      id: "58_20,21",
      field: 237
    },
    related: true
  }
];
export default {
  data() {
    return {
      nodeData: {},
      edgeData: [],
      width: 1000,
      height: 500
    };
  },
  mounted() {
    setTimeout(() => {
      this.nodeData = nodeData;
      this.edgeData = edgeData;
    }, 1000);
  },
  methods: {
    menuClick(data) {
      console.log(data.info, data.type);
    }
  },
  components: {
    // tableRelationGraph
  }
};
</script>

<style scoped></style>

但是我这个包含了比较多的业务逻辑所以看起来比较乱 @LynnAlan

Was this page helpful?
0 / 5 - 0 ratings