vue3中使用antv/G6的ER图

1 篇文章 0 订阅

这个知识点可能比较小众,先上一下例子吧:

官方是有相关的例子的,点击去查看

下面给大家省去查看官方例子的过程,直接告诉你如何写:

<script setup>
    import G6 from '@antv/g6';
    const {
      Util,
      registerBehavior,
      registerEdge,
      registerNode
    } = G6;
    let group = null;
    const isInBBox = (point, bbox) => {
      const { x,y } = point;
      const { minX, minY, maxX, maxY } = bbox;
      return x < maxX && x > minX && y > minY && y < maxY;
    };
    const itemHeight = 30;
// 滚动
registerBehavior("dice-er-scroll", {
  getDefaultCfg() {
    return {
      multiple: true,
    };
  },
  getEvents() {
    return {
      itemHeight: 50,
      wheel: "scorll",
      click: "click",
      "node:mousemove": "move",
    };
  },
  scorll(e) {
    e.preventDefault();
    const nodes = graph.getNodes().filter((n) => {
      const bbox = n.getBBox();
      return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox);
    });
    if (nodes) {
      nodes.forEach((node) => {
        const model = node.getModel();
        if (model.attrs.length < 5) {
          return;
        }
        const idx = model.startIndex || 0;
        let startX = model.startX || 0.5;
        let startIndex = idx + e.deltaY * 0.02;
        startX -= e.deltaX;
        if (startIndex < 0) {
          startIndex = 0;
        }
        if (startX > 0) {
          startX = 0;
        }
        if (startIndex > model.attrs.length - 1) {
          startIndex = model.attrs.length - 1;
        }
        graph.update(node, {
          startIndex,
          startX,
        });
      });
    }


  },
  click(e) {
    const item = e.item;
    const shape = e.shape;
    if (!item) {
      return;
    }
    if (shape.get("name") === "collapse") {
      graph.updateItem(item, {
        collapsed: true,
        size: [300, 0],
      });
    } else if (shape.get("name") === "expand") {
      graph.updateItem(item, {
        collapsed: false,
        size: [300, 0],
      });
    }
  },
  move(e) {
    const name = e.shape.get("name");
    const item = e.item;
    if (name && name.startsWith("item")) {
      graph.updateItem(item, {
        selectedIndex: Number(name.split("-")[1]),
      });
    } else {
      graph.updateItem(item, {
        selectedIndex: NaN,
      });
    }
  },
});
// 连线
registerEdge("dice-er-edge", {
  draw(cfg, group) {
    const edge = group.cfg.item;
    const sourceNode = edge.getSource().getModel();
    const targetNode = edge.getTarget().getModel();

    const sourceIndex = sourceNode.attrs.findIndex(
      (e) => e.name === cfg.sourceKey
    );

    const sourceStartIndex = sourceNode.startIndex || 0;

    let sourceY = 15;

    if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
      sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * 30;
      sourceY = Math.min(sourceY, 200);
    }

    const targetIndex = targetNode.attrs.findIndex(
      (e) => e.name === cfg.targetKey
    );

    const targetStartIndex = targetNode.startIndex || 0;

    let targetY = 15;

    if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) {
      targetY = (targetIndex - targetStartIndex + 0.5) * 30 + 30;
      targetY = Math.min(targetY, 200);
    }

    const startPoint = {
      ...cfg.startPoint
    };
    const endPoint = {
      ...cfg.endPoint
    };

    startPoint.y = startPoint.y + sourceY;
    endPoint.y = endPoint.y + targetY;

    let shape;
    if (sourceNode.id !== targetNode.id) {
      shape = group.addShape("path", {
        attrs: {
          stroke: "#5B8FF9",
          path: [
            ["M", startPoint.x, startPoint.y],
            [
              "C",
              endPoint.x / 3 + (2 / 3) * startPoint.x,
              startPoint.y,
              endPoint.x / 3 + (2 / 3) * startPoint.x,
              endPoint.y,
              endPoint.x,
              endPoint.y,
            ],
          ],
          cursor: 'pointer',
          endArrow: true,
        },
        name: "path-shape",
      });
    } else if (!sourceNode.collapsed) {
      let gap = Math.abs((startPoint.y - endPoint.y) / 3);
      if (startPoint["index"] === 1) {
        gap = -gap;
      }
      shape = group.addShape("path", {
        attrs: {
          stroke: "#5B8FF9",
          path: [
            ["M", startPoint.x, startPoint.y],
            [
              "C",
              startPoint.x - gap,
              startPoint.y,
              startPoint.x - gap,
              endPoint.y,
              startPoint.x,
              endPoint.y,
            ],
          ],
          cursor: 'pointer',
          endArrow: true,
        },
        name: "path-shape",
      });
    }
    shape.on('click', () => textFunc(cfg));
    return shape;
  },
  afterDraw(cfg, group) {
    const edge = group.cfg.item;
    const sourceNode = edge.getSource().getModel();
    const targetNode = edge.getTarget().getModel();
    if (sourceNode.collapsed && targetNode.collapsed) {
      return;
    }
    const path = group.find(
      (element) => element.get("name") === "path-shape"
    );

    const labelStyle = Util.getLabelPosition(path, 0.5, 0, 0, true);
    const label = group.addShape("text", {
      attrs: {
        ...labelStyle,
        text: cfg.label || '',
        fill: "#000",
        textAlign: "center",
        stroke: "#fff",
        lineWidth: 1,
        cursor: 'pointer'
      },
    });
    label.rotateAtStart(labelStyle.rotate);
    label.on('click', () => textFunc(cfg));
  },
});
// 盒子
registerNode("dice-er-box", {
  draw(cfg, group) {
    const boxStyle = {
      stroke: "#096DD9",
      radius: 4,
    };
    const width = 240;
    // 每个盒子list总高度
    const height = 200;
    const itemCount = 10;
    const {
      attrs = [],
      startIndex = 0,
      selectedIndex,
      collapsed,
      icon,
    } = cfg;
    const list = attrs;
    const afterList = list.slice(
      Math.floor(startIndex),
      Math.floor(startIndex + itemCount - 1)
    );
    const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30;
    group.addShape("rect", {
      attrs: {
        fill: boxStyle.stroke,
        height: 30,
        width,
        radius: [boxStyle.radius, boxStyle.radius, 0, 0],
      },
      draggable: true,
    });

    let fontLeft = 12;

    if (icon && icon.show !== false) {
      group.addShape("image", {
        attrs: {
          x: 8,
          y: 8,
          height: 16,
          width: 16,
          ...icon,
        },
      });
      fontLeft += 18;
    }
    // 每个矩形框的title
    group.addShape("text", {
      attrs: {
        y: 22,
        x: fontLeft,
        fill: "#fff",
        text: cfg.label,
        fontSize: 12,
        fontWeight: 500,
      },
    });
    // 展开收起底色框
    group.addShape("rect", {
      attrs: {
        x: 0,
        y: collapsed ? 30 : height,
        height: 15,
        width,
        fill: "#eee",
        radius: [0, 0, boxStyle.radius, boxStyle.radius],
        cursor: "pointer",
      },
      name: collapsed ? "expand" : "collapse",
    });
    // 展开收起按钮
    group.addShape("text", {
      attrs: {
        x: width / 2 - 6,
        y: (collapsed ? 30 : height) + 12,
        text: collapsed ? "+" : "-",
        width,
        fill: "#000",
        radius: [0, 0, boxStyle.radius, boxStyle.radius],
        cursor: "pointer",
      },
      name: collapsed ? "expand" : "collapse",
    });
    // 蓝色边框
    const keyshape = group.addShape("rect", {
      attrs: {
        x: 0,
        y: 0,
        width,
        height: collapsed ? 45 : height + 15,
        ...boxStyle,
      },
      draggable: true,
    });

    if (collapsed) {
      return keyshape;
    }

    const listContainer = group.addGroup({});
    listContainer.setClip({
      type: "rect",
      attrs: {
        x: -8,
        y: 30,
        width: width + 16,
        height: height - 30,
      },
    });
    listContainer.addShape({
      type: "rect",
      attrs: {
        x: 1,
        y: 30,
        width: width - 2,
        height: height - 30,
        fill: "#fff",
      },
      draggable: true,
    });

    if (list.length > itemCount) {
      const barStyle = {
        width: 4,
        padding: 0,
        boxStyle: {
          stroke: "#00000022",
        },
        innerStyle: {
          fill: "#00000022",
        },
      };

      listContainer.addShape("rect", {
        attrs: {
          y: 30,
          x: width - barStyle.padding - barStyle.width,
          width: barStyle.width,
          height: height - 30,
          ...barStyle.boxStyle,
        },
      });

      const indexHeight =
        afterList.length > itemCount ?
        (afterList.length / list.length) * height :
        10;

      listContainer.addShape("rect", {
        attrs: {
          y: 30 +
            barStyle.padding +
            (startIndex / list.length) * (height - 30),
          x: width - barStyle.padding - barStyle.width,
          width: barStyle.width,
          height: Math.min(height, indexHeight),
          ...barStyle.innerStyle,
        },
      });
    }
    if (afterList) {
      afterList.forEach((e, i) => {
        const isSelected = Math.floor(startIndex) + i === Number(selectedIndex);
        const { count, name } = e;
        const label = `${name}(${count})` //key.length > 26 ? key.slice(0, 24) + "..." : key;
        listContainer.addShape("rect", {
          attrs: {
            x: 1,
            y: i * itemHeight - itemHeight / 2 + offsetY,
            width: width - 4,
            height: itemHeight,
            radius: 2,
            lineWidth: 1,
            cursor: "pointer",
          },
          name: `item-${Math.floor(startIndex) + i}-content`,
          draggable: true,
        });

        if (!cfg.hideDot) {
          listContainer.addShape("circle", {
            attrs: {
              x: 0,
              y: i * itemHeight + offsetY,
              r: 3,
              stroke: boxStyle.stroke,
              fill: "white",
              radius: 2,
              lineWidth: 1,
              cursor: "pointer",
            },
          });
          listContainer.addShape("circle", {
            attrs: {
              x: width,
              y: i * itemHeight + offsetY,
              r: 3,
              stroke: boxStyle.stroke,
              fill: "white",
              radius: 2,
              lineWidth: 1,
              cursor: "pointer",
            },
          });
        }

        const textClick = listContainer.addShape("text", {
          attrs: {
            x: 12,
            y: i * itemHeight + offsetY + 6,
            text: label,
            fontSize: 12,
            fill: "#000",
            fontFamily: "Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol",
            full: e,
            fontWeight: isSelected ? 500 : 100,
            cursor: "pointer",
          },
          name: `item-${Math.floor(startIndex) + i}`,
        });
        textClick.on('click', () => textFunc(e));
      });
    }
    return keyshape;
  },
  getAnchorPoints() {
    return [
      [0, 0],
      [1, 0],
    ];
  },
});
const textFunc = (vals) => {
  console.log(vals)
}
const dataTransform = (data) => {
  const nodes = [];
  const edges = [];
  if(data.length){
    data.map((item) => {
      nodes.push({
        ...item
      });
      if (item.attrs) {
        item.attrs.forEach((attr) => {
          if (attr.relation && attr.relation.length) {
            attr.relation.forEach((relation) => {
              edges.push({
                source: item.id, //矩形框的唯一值
                sourceKey: attr.name, // 矩形框里每条数据的唯一值
                target: relation.node, // 指向的矩形框的唯一值(relation.node就是node.id)
                targetKey: relation.name, // 指向的矩形框里每条数据的唯一值(relation.name就是attr.name)
                label: relation.count,
                items: relation
              });
            });
          }
        });
      }
    });
  }
  return {
    nodes,
    edges,
  };
const getList = () => {
  获取数据的方法(传入你的参数).then(res=>{
    obj.datas = res.code === 200 ? res.data : [];
    graph.changeData(dataTransform(obj.datas));
  });
}
onMounted(() => {  
  if(document.getElementById('excavateContainer')){
    const container = document.getElementById('excavateContainer');
    const width = container.scrollWidth;
    const height = container.scrollHeight || 300;
    if(graph){
      graph.destroy();
    }
    graph = null
    graph = new G6.Graph({
      container: 'excavateContainer',
      width,
      height,
      defaultNode: {
        size: [240, 0],
        type: 'dice-er-box',
        color: '#5B8FF9',
        style: {
          fill: '#9EC9FF',
          lineWidth: 3,
        },
        labelCfg: {
          style: {
            fill: 'black',
            fontSize: 20,
          },
        },
      },
      defaultEdge: {
        type: 'dice-er-edge',
        style: {
          stroke: '#e2e2e2',
          lineWidth: 4,
          endArrow: true,
        },
      },
      modes: {
        default: ['dice-er-scroll', 'drag-node', 'drag-canvas'],
      },
      layout: {
        type: 'dagre',
        rankdir: 'LR',
        align: 'UL',
        controlPoints: true,
        nodesepFunc: () => 0.2,
        ranksepFunc: () => 0.5,
      },
      animate: true,
    })
    graph.data(dataTransform(obj.datas));
    graph.render();
  }
});
</script>
<template>
    <div id="excavateContainer" />
</template>

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

maxenjoycode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值