Bladeren bron

feat: 添加树状图

liaojiaxing 6 maanden geleden
bovenliggende
commit
8a83354ac0

+ 22 - 22
apps/designer/src/components/mindMap/Topic.tsx

@@ -95,28 +95,28 @@ const component = ({ node, graph }: { node: Node; graph: Graph }) => {
     };
   }, []);
 
-  useEffect(() => {
-    if (size.height && size.width) {
-      const w = Math.max(
-        size.width,
-        titleRef.current?.clientWidth || 0,
-        tagRef.current?.clientWidth || 0,
-        extraModuleRef.current?.clientWidth || 0
-      );
-      const h = Math.max(
-        size.height,
-        titleRef.current?.clientHeight || 0,
-        tagRef.current?.clientHeight || 0,
-        extraModuleRef.current?.clientHeight || 0
-      );
-      if (h !== node.size().height || w !== node.size().width) {
-        node.setData({
-          width: w,
-        });
-        node.size(w, h);
-      }
-    }
-  }, [size, titleRef, tagRef, extraModuleRef]);
+  // useEffect(() => {
+  //   if (size.height && size.width) {
+  //     const w = Math.max(
+  //       size.width,
+  //       titleRef.current?.clientWidth || 0,
+  //       tagRef.current?.clientWidth || 0,
+  //       extraModuleRef.current?.clientWidth || 0
+  //     );
+  //     const h = Math.max(
+  //       size.height,
+  //       titleRef.current?.clientHeight || 0,
+  //       tagRef.current?.clientHeight || 0,
+  //       extraModuleRef.current?.clientHeight || 0
+  //     );
+  //     if (h !== node.size().height || w !== node.size().width) {
+  //       node.setData({
+  //         width: w,
+  //       });
+  //       node.size(w, h);
+  //     }
+  //   }
+  // }, [size, titleRef, tagRef, extraModuleRef]);
 
   const handleResize = () => {
     node.setData({

+ 5 - 5
apps/designer/src/pages/mindmap/components/Config/Structure.tsx

@@ -9,11 +9,11 @@ const structures = [
     icon: "icon-Frame1",
     type: StructureType.leftRight,
   },
-  {
-    name: "自由分布",
-    icon: "icon-ziyoubuju",
-    type: StructureType.free,
-  },
+  // {
+  //   name: "自由分布",
+  //   icon: "icon-ziyoubuju",
+  //   type: StructureType.free,
+  // },
   {
     name: "右侧分布",
     icon: "icon-siweidaotubianjiqi",

+ 79 - 14
apps/designer/src/pages/mindmap/edge.ts

@@ -37,9 +37,11 @@ Graph.registerConnector(
 // 直线
 Graph.registerConnector(
   "straight-branch-connector",
-  function(sourcePoint, targetPoint, routerPoints, options){
+  function (sourcePoint, targetPoint, routerPoints, options) {
     const halfWidth = this.sourceBBox.width / 2;
-    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth-10 );
+    const midX =
+      sourcePoint.x +
+      (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth - 10);
     const midY = sourcePoint.y;
     const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
@@ -52,8 +54,8 @@ Graph.registerConnector(
 );
 Graph.registerConnector(
   "straight-sub-connector",
-  function(sourcePoint, targetPoint, routerPoints, options) {
-    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10 );
+  function (sourcePoint, targetPoint, routerPoints, options) {
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10);
     const midY = sourcePoint.y;
     const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
@@ -67,9 +69,11 @@ Graph.registerConnector(
 // 圆角折线
 Graph.registerConnector(
   "rounded-branch-connector",
-  function(sourcePoint, targetPoint, routerPoints, options) {
+  function (sourcePoint, targetPoint, routerPoints, options) {
     const halfWidth = this.sourceBBox.width / 2;
-    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth-10 );
+    const midX =
+      sourcePoint.x +
+      (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth - 10);
     const midY = sourcePoint.y;
     const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
@@ -85,12 +89,15 @@ Graph.registerConnector(
 Graph.registerConnector(
   "rounded-sub-connector",
   (sourcePoint, targetPoint, routerPoints, options) => {
-    const midX = sourcePoint.x < targetPoint.x ? sourcePoint.x + 10 : sourcePoint.x - 10;
+    const midX =
+      sourcePoint.x < targetPoint.x ? sourcePoint.x + 10 : sourcePoint.x - 10;
     const midY = sourcePoint.y;
+    // 可能存在误差
+    const deviationY = Math.abs(targetPoint.y - sourcePoint.y);
     const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
      L ${midX} ${midY}
-     L ${midX} ${targetPoint.y + (targetPoint.y < sourcePoint.y ? 5 : targetPoint.y === sourcePoint.y ? 0 : -5)}
+     L ${midX} ${targetPoint.y + (deviationY < 10 ? 0 : targetPoint.y < sourcePoint.y ? 5 : -5)}
      Q ${midX} ${targetPoint.y} ${midX + (targetPoint.x < sourcePoint.x ? -5 : 5)} ${targetPoint.y}
      L ${targetPoint.x} ${targetPoint.y}
     `;
@@ -101,9 +108,11 @@ Graph.registerConnector(
 // 折线
 Graph.registerConnector(
   "poly-branch-connector",
-  function(sourcePoint, targetPoint, routerPoints, options) {
+  function (sourcePoint, targetPoint, routerPoints, options) {
     const halfWidth = this.sourceBBox.width / 2;
-    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth-10 );
+    const midX =
+      sourcePoint.x +
+      (sourcePoint.x < targetPoint.x ? halfWidth + 10 : -halfWidth - 10);
     const midY = sourcePoint.y;
     const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
@@ -117,8 +126,8 @@ Graph.registerConnector(
 );
 Graph.registerConnector(
   "poly-sub-connector",
-  function(sourcePoint, targetPoint, routerPoints, options) {
-    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10 );
+  function (sourcePoint, targetPoint, routerPoints, options) {
+    const midX = sourcePoint.x + (sourcePoint.x < targetPoint.x ? 10 : -10);
     const midY = sourcePoint.y;
     const pathData = `
      M ${sourcePoint.x} ${sourcePoint.y}
@@ -131,12 +140,42 @@ Graph.registerConnector(
   true
 );
 
+// 树形
+Graph.registerConnector(
+  "tree-branch-connector",
+  function (sourcePoint, targetPoint, routerPoints, options) {
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${sourcePoint.x} ${sourcePoint.y + 10}
+     L ${targetPoint.x} ${sourcePoint.y + 10}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+Graph.registerConnector(
+  "tree-sub-connector",
+  function (sourcePoint, targetPoint, routerPoints, options) {
+    const pathData = `
+     M ${sourcePoint.x} ${sourcePoint.y}
+     L ${sourcePoint.x} ${targetPoint.y}
+     L ${targetPoint.x} ${targetPoint.y}
+    `;
+    return options.raw ? Path.parse(pathData) : pathData;
+  },
+  true
+);
+
 const getConnector = (
   structure: StructureType,
   theme: string,
   type: TopicType
 ) => {
   // TODO根据结构处理连接线
+  if(structure === StructureType.tree) {
+    return `tree-${type}-connector`
+  }
   const themeObj = getTheme(theme);
   if (type === TopicType.branch) {
     return `${themeObj.branchConnector}-${type}-connector`;
@@ -165,6 +204,23 @@ const getSourceAnchor = (type: TopicType, structure: StructureType) => {
             name: "right",
           };
     }
+    case StructureType.leftRight: {
+    }
+    case StructureType.tree: {
+      return type === TopicType.branch
+        ? {
+            name: "bottom",
+            args: {
+              dy: -5
+            }
+          }
+        : {
+            name: "bottomLeft",
+            args: {
+              dx: 10,
+            },
+          };
+    }
     case StructureType.leftBracket:
     case StructureType.leftFishbone:
     case StructureType.leftTreeShape:
@@ -185,11 +241,20 @@ const getTargetAnchor = (type: TopicType, structure: StructureType) => {
   switch (structure) {
     case StructureType.left: {
       return {
-            name: "right",
-          };
+        name: "right",
+      };
     }
     case StructureType.right: {
       return {
+        name: "left",
+      };
+    }
+    case StructureType.tree: {
+      return type === TopicType.branch
+        ? {
+            name: "top",
+          }
+        : {
             name: "left",
           };
     }

+ 29 - 6
apps/designer/src/pages/mindmap/hierarchy.ts

@@ -1,6 +1,7 @@
 import { StructureType, TopicType } from "@/enum";
 import { MindMapProjectInfo, TopicItem } from "@/types";
 import Hierarchy from "@antv/hierarchy";
+import { getTreeStructure } from "@/utils/hierarchy/tree";
 
 // 思维导图结构实现
 export const hierarchyMethodMap: Record<
@@ -21,9 +22,9 @@ export const hierarchyMethodMap: Record<
         return d.width;
       },
       getHGap(d: TopicItem) {
-        if(d.type === TopicType.main) return pageSetting.branchX || 20;
-        if(d.type === TopicType.branch) return pageSetting.subTopicX || 20;
-        if(d.type === TopicType.sub) return pageSetting.subTopicX || 20;
+        if (d.type === TopicType.main) return pageSetting.branchX || 20;
+        if (d.type === TopicType.branch) return pageSetting.subTopicX || 20;
+        if (d.type === TopicType.sub) return pageSetting.subTopicX || 20;
         return pageSetting.branchX || 40;
       },
       getVGap() {
@@ -48,9 +49,9 @@ export const hierarchyMethodMap: Record<
         return d.width;
       },
       getHGap(d: TopicItem) {
-        if(d.type === TopicType.main) return pageSetting.branchX || 20;
-        if(d.type === TopicType.branch) return pageSetting.subTopicX || 20;
-        if(d.type === TopicType.sub) return pageSetting.subTopicX || 20;
+        if (d.type === TopicType.main) return pageSetting.branchX || 20;
+        if (d.type === TopicType.branch) return pageSetting.subTopicX || 20;
+        if (d.type === TopicType.sub) return pageSetting.subTopicX || 20;
         return pageSetting.branchX || 40;
       },
       getVGap() {
@@ -61,4 +62,26 @@ export const hierarchyMethodMap: Record<
       },
     });
   },
+  // 左右分布
+  [StructureType.leftRight]: <T>(
+    topic: TopicItem,
+    pageSetting: MindMapProjectInfo["pageSetting"]
+  ): T => {
+    if (topic.type !== TopicType.main) {
+      return hierarchyMethodMap[StructureType.left](topic, pageSetting);
+    } else {
+      const rightChildren = (topic.children || []).filter((item, index) => index % 2 === 0);
+      const leftChildren = (topic.children || []).filter((item, index) => index % 2 === 1);
+      const left = hierarchyMethodMap[StructureType.left]({...topic, children: leftChildren}, pageSetting);
+      const right = hierarchyMethodMap[StructureType.right]({...topic, children: rightChildren}, pageSetting);
+      return {
+        ...right,
+        children: [...right.children, ...left.children]
+      }
+    }
+  },
+  // 树状结构
+  [StructureType.tree]: <T>(topic: TopicItem, pageSetting: MindMapProjectInfo['pageSetting']): T => {
+    return getTreeStructure<T>(topic, pageSetting);
+  }
 };

+ 4 - 1
apps/designer/src/pages/mindmap/index.less

@@ -20,7 +20,7 @@
   & > div {
     display: none;
   }
-  & > [data-position="right"], & > [data-position="left"]  {
+  & > [data-position="right"], & > [data-position="left"], & > [data-position="bottom"]  {
     display: block;
     border: none;
     width: 1px;
@@ -30,6 +30,9 @@
     right: -1px;
     opacity: 0;
   }
+  & > [data-position="left"] {
+    left: 0;
+  }
 } 
 
 .x6-widget-transform > div:hover {

+ 1 - 8
apps/designer/src/pages/mindmap/mindMap.tsx

@@ -1,5 +1,5 @@
 import { TopicType } from "@/enum";
-import { MindMapProjectInfo, TopicItem } from "@/types";
+import { MindMapProjectInfo, TopicItem, HierarchyResult } from "@/types";
 import { Graph, Cell, Node } from "@antv/x6";
 import TopicComponent from "@/components/mindMap/Topic";
 import { topicData } from "@/config/data";
@@ -9,13 +9,6 @@ import { createEdge } from "./edge";
 import { getTheme } from "./theme";
 import { topicMenu } from "@/utils/contentMenu";
 
-interface HierarchyResult {
-  id: string;
-  x: number;
-  y: number;
-  data: TopicItem;
-  children?: HierarchyResult[];
-}
 /**
  * 渲染思维导图项目
  * @param graph

+ 2 - 0
apps/designer/src/pages/mindmap/theme.ts

@@ -38,6 +38,7 @@ export const getTheme = (key?: string, parentNode?: Node): Record<string, any> =
         },
         edge: {
           color: color1,
+          width: 3,
         }
       },
       sub: {
@@ -54,6 +55,7 @@ export const getTheme = (key?: string, parentNode?: Node): Record<string, any> =
         },
         edge: {
           color: parentNode?.data?.type === TopicType.branch ? parentNode?.data?.fill?.color1 : parentNode?.data?.edge?.color || '#323232',
+          width: 2,
         }
       },
       branchConnector: MindmapConnectorType.curve,

+ 12 - 0
apps/designer/src/types.d.ts

@@ -46,6 +46,18 @@ export interface cellStyle {
   };
 }
 
+export interface HierarchyResult {
+  id: string;
+  x: number;
+  y: number;
+  data: TopicItem;
+  width: number;
+  height: number;
+  children: HierarchyResult[];
+  totalHeight: number;
+  totalWidth: number;
+}
+
 export type TopicItem = {
   /**
    * 主键

+ 86 - 0
apps/designer/src/utils/hierarchy/tree.ts

@@ -0,0 +1,86 @@
+import { TopicType } from "@/enum";
+import { MindMapProjectInfo, TopicItem, HierarchyResult } from "@/types";
+
+const DISTANCE = 20; // 子节点相对于父节点的偏差
+function firstWalk(node: TopicItem): HierarchyResult {
+  const root: HierarchyResult = {
+    x: 0,
+    y: 0,
+    totalHeight: 0,
+    totalWidth: 0,
+    width: node.width,
+    height: node.height,
+    id: node.id,
+    data: node,
+    children: [],
+  };
+
+  root.children = node.children?.map((child) => firstWalk(child)) || [];
+
+  return root;
+}
+
+// 设置宽高
+function secondWalk(
+  node: HierarchyResult,
+  pageSetting: MindMapProjectInfo["pageSetting"]
+) {
+  let totalHeight = node.height;
+  let totalWidth = node.data.type === TopicType.main ? 0 : node.width;
+  if (node.children?.length) {
+    node.children.forEach((c: HierarchyResult, index) => {
+      const childSize = secondWalk(c, pageSetting);
+      const offsetY = node.data.type === TopicType.main ? pageSetting.branchY :  pageSetting.subTopicY;
+      // 总的高度 = 自身高度 + 子节点的高度 + 节点之间的距离
+      totalHeight += childSize.height + offsetY;
+      
+      if (node.data.type === TopicType.main) {
+        const offsetX = index < node.children.length - 1 ? pageSetting.branchX : 0;
+        totalWidth += childSize.width + offsetX;
+      } else {
+        // 总的宽度 = 子节点的宽度与自身宽度取最大值
+        totalWidth = Math.max(DISTANCE + childSize.width, totalWidth);
+      }
+    });
+  }
+  node.totalHeight = totalHeight;
+  node.totalWidth = totalWidth;
+
+  return {
+    height: totalHeight,
+    width: Math.max(totalWidth, node.width),
+  };
+}
+
+function thirdWalk(node: HierarchyResult, pageSetting: MindMapProjectInfo['pageSetting']) {
+  let startX = node.width / 2 - node.totalWidth / 2;
+  node.children.forEach((child) => {
+    child.y = node.y + node.height + pageSetting.branchY;
+    child.x = startX;
+    startX += child.totalWidth + pageSetting.subTopicX;
+    fourthWalk(child, pageSetting);
+  });
+}
+
+function fourthWalk(node: HierarchyResult, pageSetting: MindMapProjectInfo['pageSetting']) {
+  let startY = node.y + node.height + pageSetting.subTopicY;
+  node.children.forEach((child) => {
+    child.y = startY;
+    child.x = node.x + DISTANCE;
+    startY += child.totalHeight + pageSetting.subTopicY;
+    if(child.children.length) {
+      fourthWalk(child, pageSetting);
+    }
+  });
+}
+
+export const getTreeStructure = <K>(
+  topic: TopicItem,
+  pageSetting: MindMapProjectInfo["pageSetting"]
+): K => {
+  const root = firstWalk(topic);
+  secondWalk(root, pageSetting);
+  thirdWalk(root, pageSetting);
+  console.log(root);
+  return root as K;
+};