树状图例子

186 阅读4分钟

通过G6实现

官网:antv-g6.gitee.io/zh/examples…

下载"@antv/g6": "4.8.24",

父组件

import React, { useEffect } from "react";
import TreeGraphComponent from "./flowGraph";
const data = {
  id: "root",
  label: "root",
  children: [
    {
      id: "c1",
      label: "c1",
      children: [
        {
          id: "c1-1",
          label: "c1-1",
        },
        {
          id: "c1-2",
          label: "c1-2",
          children: [
            {
              id: "c1-2-1",
              label: "c1-2-1",
            },
            {
              id: "c1-2-2",
              label: "c1-2-2",
            },
          ],
        },
      ],
    },
    {
      id: "c2",
      label: "c2",
    },
    {
      id: "c3",
      label: "c3",
      children: [
        {
          id: "c3-1",
          label: "c3-1",
        },
        {
          id: "c3-2",
          label: "c3-2",
          children: [
            {
              id: "c3-2-1",
              label: "c3-2-1",
            },
            {
              id: "c3-2-2",
              label: "c3-2-2",
            },
            {
              id: "c3-2-3",
              label: "c3-2-3",
            },
          ],
        },
        {
          id: "c3-3",
          label: "c3-3",
        },
      ],
    },
  ],
};

function App() {
  return (
    <div style={{ width: "100%", height: "100%" }}>
      <TreeGraphComponent data={data} />
    </div>
  );
}

export default App;

子组件

import React, { useRef, useState, useEffect } from "react";
import G6, { Grid, TreeGraphData } from "@antv/g6";

const TreeGraphComponent = (props: any) => {
  const { data } = props;
  const containerRef = useRef(null); //获取画布元素
  const graphRef = useRef(null); //储存graph实例
  const COLLAPSE_ICON = function COLLAPSE_ICON(x: any, y: any, r: any) {
    return [
      ["M", x - r, y - r],
      ["a", r, r, 0, 1, 0, r * 2, 0],
      ["a", r, r, 0, 1, 0, -r * 2, 0],
      ["M", x + 2 - r, y - r],
      ["L", x + r - 2, y - r],
    ];
  };
  const EXPAND_ICON = function EXPAND_ICON(x: any, y: any, r: any) {
    return [
      ["M", x - r, y - r],
      ["a", r, r, 0, 1, 0, r * 2, 0],
      ["a", r, r, 0, 1, 0, -r * 2, 0],
      ["M", x + 2 - r, y - r],
      ["L", x + r - 2, y - r],
      ["M", x, y - 2 * r + 2],
      ["L", x, y - 2],
    ];
  };
  const getShapeStyle = (cfg: any) => {
    let cfgTmp = {
      labelCfg: null,
      leftIcon: null,
      style: null,
      width: null,
      height: null,
    };
    cfgTmp.labelCfg = cfg.labelCfg;
    cfgTmp.leftIcon = cfg.leftIcon;
    cfgTmp.style = cfg.style;
    cfgTmp.width = cfg.x;
    cfgTmp.height = cfg.y;
    return cfgTmp;
  };
  useEffect(() => {
    console.log("useEffect11111");
    if (!graphRef.current && containerRef.current) {
      const offsetWidth = containerRef.current.offsetWidth;
      const offsetHeight = containerRef.current.offsetHeight;
      console.log("offsetWidth====", offsetWidth);
      console.log("containerRef.current===", containerRef.current);
      // // 创建 TreeGraph 实例
      // graphRef.current = new G6.TreeGraph({
      //   container: containerRef.current,
      //   width: 800,
      //   height: 600,
      //   // plugins: [grid],
      //   modes: {
      //     default: ["drag-canvas", "zoom-canvas"],
      //   },
      //   defaultEdge: {
      //     type: "cubic-horizontal",
      //     style: {
      //       stroke: "#A3B1BF",
      //     },
      //   },
      //   layout: {
      //     type: "compactBox",
      //     direction: "TB",
      //     getId: function getId(d: any) {
      //       return d.id;
      //     },
      //     getHeight: function getHeight() {
      //       return 16;
      //     },
      //     getWidth: function getWidth() {
      //       return 16;
      //     },
      //     getVGap: function getVGap() {
      //       return 40;
      //     },
      //     getHGap: function getHGap() {
      //       return 70;
      //     },
      //   },
      // });
      const defaultStateStyles = {
        hover: {
          stroke: "#1890ff",
          lineWidth: 2,
        },
      };

      const defaultNodeStyle = {
        fill: "#91d5ff",
        stroke: "#40a9ff",
        radius: 5,
      };

      const defaultEdgeStyle = {
        stroke: "#91d5ff",
        endArrow: {
          path: "M 0,0 L 12, 6 L 9,0 L 12, -6 Z",
          fill: "#91d5ff",
          d: -20,
        },
      };

      const defaultLayout = {
        type: "compactBox",
        direction: "TB",
        getId: function getId(d: any) {
          return d.id;
        },
        getHeight: function getHeight() {
          return 16;
        },
        getWidth: function getWidth() {
          return 16;
        },
        getVGap: function getVGap() {
          return 40;
        },
        getHGap: function getHGap() {
          return 70;
        },
      };

      const defaultLabelCfg = {
        style: {
          fill: "#000",
          fontSize: 12,
        },
      };
      G6.Util.traverseTree(data, (d: any) => {
        d.leftIcon = {
          style: {
            fill: "#e6fffb",
            stroke: "#e6fffb",
          },
          img: "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ",
        };
        return true;
      });
      // 注册自定义节点
      G6.registerNode(
        "icon-node",
        {
          options: {
            size: [60, 20],
            stroke: "#91d5ff",
            fill: "#91d5ff",
          },

          draw(cfg, group) {
            const w = 242;
            const h = 104;
            const styles = getShapeStyle(cfg);
            // const { labelCfg = {}, leftIcon:any = null } = cfg;
            const leftIcon: any = cfg.leftIcon;
            const keyShape = group.addShape("rect", {
              attrs: {
                x: -w / 2,
                y: -h / 2,
                width: 120,
                height: 40,
                stroke: "#44ACFC", //边框颜色
                fill: "#94D4FC", //背景颜色
                radius: 2,
                // labelCfg: { style: { fill: "#000", fontSize: 12 } },
                // leftIcon: { style: { fill: "#e6fffb", stroke: "#e6fffb" } },
                // fill: "#91d5ff",
                // radius: 5,
                // stroke: "#40a9ff",
                hover: {
                  lineWidth: 2,
                  // stroke: "red",
                },
              },
            });
            /**
             * leftIcon 格式如下:
             *  {
             *    style: ShapeStyle;
             *    img: ''
             *  }
             */
            if (leftIcon) {
              const { style, img } = leftIcon;
              group.addShape("rect", {
                attrs: {
                  x: 1 - w / 2,
                  y: 1 - h / 2,
                  width: 38,
                  height: 40 - 2,
                  fill: "#8c8c8c",
                  radius: [2, 2],
                  ...style,
                },
              });

              group.addShape("image", {
                attrs: {
                  x: 8 - w / 2,
                  y: 8 - h / 2,
                  width: 24,
                  height: 24,
                  img:
                    img ||
                    "https://g.alicdn.com/cm-design/arms-trace/1.0.155/styles/armsTrace/images/TAIR.png",
                },
                name: "image-shape",
              });
            }

            // 如果不需要动态增加或删除元素,则不需要 add 这两个 marker
            group.addShape("marker", {
              attrs: {
                x: 40 - w / 2,
                y: 52 - h / 2,
                r: 6,
                stroke: "#73d13d",
                cursor: "pointer",
                symbol: EXPAND_ICON,
              },
              name: "add-item",
            });
            //删除元素
            group.addShape("marker", {
              attrs: {
                x: 80 - w / 2,
                y: 52 - h / 2,
                r: 6,
                stroke: "#ff4d4f",
                cursor: "pointer",
                symbol: COLLAPSE_ICON,
              },
              name: "remove-item",
            });

            if (cfg.label) {
              group.addShape("text", {
                attrs: {
                  fill: "red", //label中字体颜色
                  fontSize: 12, //label中字体大小
                  text: cfg.label,
                  x: 50 - w / 2,
                  y: 25 - h / 2,
                },
              });
            }

            return keyShape;
          },

          update: undefined,
        },
        "rect"
      );

      G6.registerEdge("flow-line", {
        draw(cfg, group) {
          const startPoint = cfg.startPoint;
          const endPoint = cfg.endPoint;

          const { style } = cfg;
          const shape = group.addShape("path", {
            attrs: {
              stroke: style.stroke,
              endArrow: style.endArrow,
              path: [
                ["M", startPoint.x, startPoint.y],
                ["L", startPoint.x, (startPoint.y + endPoint.y) / 2],
                ["L", endPoint.x, (startPoint.y + endPoint.y) / 2],
                ["L", endPoint.x, endPoint.y],
              ],
            },
          });

          return shape;
        },
      });
      const minimap = new G6.Minimap({
        size: [150, 100],
      });
      const grid = new G6.Grid({
        // img: "http://localhost:3000/static/media/logo.fc2116e0bf414cf5e6b7.png",
      });
      console.log("grid0000", grid);
      const graph = new G6.TreeGraph({
        container: containerRef.current,
        width: offsetWidth,
        height: offsetHeight,
        linkCenter: true,
        // plugins: [minimap, grid],
        plugins: [grid],
        modes: {
          default: ["drag-canvas", "zoom-canvas"],
        },
        defaultNode: {
          type: "icon-node",
          size: [40, 40],
          style: defaultNodeStyle,
          labelCfg: defaultLabelCfg,
        },
        defaultEdge: {
          type: "flow-line",
          style: defaultEdgeStyle,
        },
        nodeStateStyles: defaultStateStyles,
        edgeStateStyles: defaultStateStyles,
        layout: defaultLayout,
      });
      // 加载数据
      // graph.data(data);
      // // 渲染树图
      // graph.render();
      graph.read(data);

      // if (containerRef.current.getElementsByTagName("canvas")) {
      //   const canvasRefList =
      //     containerRef.current.getElementsByTagName("canvas");
      //   console.log(
      //     "canvas000000",
      //     canvasRefList[0].width,
      //     canvasRefList[0].style.width
      //   );
      //   console.log(
      //     "qqqqqqqq",
      //     canvasRefList[0].style.width,
      //     String(offsetWidth) + "px",
      //     canvasRefList[0].style.width == String(offsetWidth) + "px"
      //   );
      //   if (canvasRefList[0].style.width != String(offsetWidth) + "px") {
      //     console.log(
      //       canvasRefList[0].style.width == String(offsetWidth) + "px"
      //     );
      //     canvasRefList[0].style.width = String(offsetWidth) + "px";
      //   }
      //   // canvasRefList[0].width = offsetWidth;
      // }

      // 返回值zoom表示当前视口的缩放比例
      const zoom = graph.getZoom();
      console.log("zoom000000", zoom);

      //让画布内容适应视口。
      // graph.fitView(20);

      // 在渲染和动画完成后调用(平移图到中心将对齐到画布中心,但不缩放。优先级低于 fitView。)
      graph.fitCenter();
      graphRef.current = graph;
      console.log("qqq");
      //鼠标hover事件移入模块
      graph.on("node:mouseenter", (evt) => {
        const { item } = evt;
        if (item) {
          graph.setItemState(item, "hover", true);
        }
      });
      //鼠标hover事件移出模块
      graph.on("node:mouseleave", (evt) => {
        const { item } = evt;
        if (item) {
          graph.setItemState(item, "hover", false);
        }
      });
      graph.on("node:click", (evt) => {
        const { item, target } = evt;
        const targetType = target.get("type");
        const name = target.get("name");
        // 更新节点名称
        if (targetType != "marker" && item) {
          const nodeName = prompt("请输入新的节点名称:");
          if (nodeName) {
            graph.update(
              item,
              {
                label: nodeName,
              },
              false
            );
          }
        }
        // 增加元素
        if (targetType === "marker" && item) {
          let model: TreeGraphData = item.getModel() as TreeGraphData;
          if (name === "add-item") {
            if (!model.children) {
              model.children = [];
            }
            const id = `n-${Math.random()}`;
            model.children.push({
              id,
              // label: id.substr(0, 8),
              label: id.substring(0, 8),
              leftIcon: {
                style: {
                  fill: "#e6fffb",
                  stroke: "#e6fffb",
                },
                img: "https://gw.alipayobjects.com/mdn/rms_f8c6a0/afts/img/A*Q_FQT6nwEC8AAAAAAAAAAABkARQnAQ",
              },
            });
            graph.updateChild(model, model.id);
          } else if (name === "remove-item") {
            //删减元素
            graph.removeChild(model.id);
          }
        }
      });
    }
    // 清理函数
    return () => {
      console.log("returnreturnreturnreturnreturnreturn");
      // if (graphRef.current) {
      //   graphRef.current.destroy();
      //   graphRef.current = null;
      // }
    };
  }, []);
  const apply = () => {
    console.log("000000");
    console.log("graphRef.current0000", graphRef.current);
  };

  return (
    <div
      ref={containerRef}
      style={{ width: "100%", height: "600px", border: "1px solid red" }}
    >
      <button
        onClick={() => {
          apply();
        }}
        style={{ position: "absolute" }}
      >
        完成
      </button>
    </div>
  );
};

export default TreeGraphComponent;