This commit is contained in:
longdayi 2025-01-21 20:38:10 +08:00
parent ae5ab6b649
commit 334f6cdf19
3 changed files with 141 additions and 38 deletions

View File

@ -7,13 +7,118 @@ interface LayoutOptions {
nodeSeparation?: number; nodeSeparation?: number;
} }
interface NodeWithLayout extends Node {
width?: number;
height?: number;
children?: NodeWithLayout[];
parent?: NodeWithLayout;
subtreeHeight?: number;
isRight?: boolean;
}
export function getMindMapLayout(options: LayoutOptions) { export function getMindMapLayout(options: LayoutOptions) {
const {
nodes,
edges,
levelSeparation = 200,
nodeSeparation = 60
} = options;
// 构建树形结构
const nodeMap = new Map<string, NodeWithLayout>();
nodes.forEach(node => {
nodeMap.set(node.id, { ...node, children: [], width: 150, height: 40 });
});
let rootNode: NodeWithLayout | undefined;
edges.forEach(edge => {
const source = nodeMap.get(edge.source);
const target = nodeMap.get(edge.target);
if (source && target) {
source.children?.push(target);
target.parent = source;
}
});
// 找到根节点
rootNode = Array.from(nodeMap.values()).find(node => !node.parent);
if (!rootNode) return { nodes, edges };
// 分配节点到左右两侧
function assignSides(node: NodeWithLayout, isRight: boolean = true) {
if (!node.children?.length) return;
const len = node.children.length;
const midIndex = Math.floor(len / 2);
// 如果是根节点,将子节点分为左右两部分
if (!node.parent) {
for (let i = 0; i < len; i++) {
const child = node.children[i];
assignSides(child, i < midIndex);
child.isRight = i < midIndex;
}
}
// 如果不是根节点,所有子节点继承父节点的方向
else {
node.children.forEach(child => {
assignSides(child, isRight);
child.isRight = isRight;
});
}
}
// 计算子树高度
function calculateSubtreeHeight(node: NodeWithLayout): number {
if (!node.children?.length) {
node.subtreeHeight = node.height || 40;
return node.subtreeHeight;
}
const childrenHeight = node.children.reduce((sum, child) => {
return sum + calculateSubtreeHeight(child);
}, 0);
const totalGaps = (node.children.length - 1) * nodeSeparation;
node.subtreeHeight = Math.max(node.height || 40, childrenHeight + totalGaps);
return node.subtreeHeight;
}
// 布局计算
function calculateLayout(node: NodeWithLayout, x: number, y: number) {
node.position = { x, y };
if (!node.children?.length) return;
let currentY = y - (node.subtreeHeight || 0) / 2;
node.children.forEach(child => {
const direction = child.isRight ? 1 : -1;
const childX = x + (levelSeparation * direction);
const childY = currentY + (child.subtreeHeight || 0) / 2;
calculateLayout(child, childX, childY);
currentY += (child.subtreeHeight || 0) + nodeSeparation;
});
}
// 执行布局流程
if (rootNode) {
// 1. 分配节点到左右两侧
assignSides(rootNode);
// 2. 计算子树高度
calculateSubtreeHeight(rootNode);
// 3. 执行布局计算
calculateLayout(rootNode, 0, 0);
}
// 转换回原始格式
const layoutedNodes = Array.from(nodeMap.values()).map(node => ({
...node,
position: node.position,
}));
return { return {
nodes: options.nodes, nodes: layoutedNodes,
edges: options.edges, edges,
};
}
} }

View File

@ -116,9 +116,7 @@ export const GraphNode = memo(({ id, selected, data, isConnectable }: NodeProps<
${isEditing ? 'ring-2 ring-white/50' : ''} ${isEditing ? 'ring-2 ring-white/50' : ''}
`} `}
> >
<div className={`p-2 ${baseTextStyles}`}>
{data.label}
</div>
<textarea <textarea
ref={textareaRef} ref={textareaRef}
defaultValue={data.label} defaultValue={data.label}

View File

@ -10,28 +10,28 @@ services:
- POSTGRES_PASSWORD=Letusdoit000 - POSTGRES_PASSWORD=Letusdoit000
volumes: volumes:
- ./volumes/postgres:/var/lib/postgresql/data - ./volumes/postgres:/var/lib/postgresql/data
minio: # minio:
image: minio/minio # image: minio/minio
ports: # ports:
- "9000:9000" # - "9000:9000"
- "9001:9001" # - "9001:9001"
volumes: # volumes:
- ./volumes/minio:/minio_data # - ./volumes/minio:/minio_data
environment: # environment:
- MINIO_ACCESS_KEY=minioadmin # - MINIO_ACCESS_KEY=minioadmin
- MINIO_SECRET_KEY=minioadmin # - MINIO_SECRET_KEY=minioadmin
command: minio server /minio_data --console-address ":9001" -address ":9000" # command: minio server /minio_data --console-address ":9001" -address ":9000"
healthcheck: # healthcheck:
test: # test:
[ # [
"CMD", # "CMD",
"curl", # "curl",
"-f", # "-f",
"http://192.168.2.1:9001/minio/health/live" # "http://192.168.2.1:9001/minio/health/live"
] # ]
interval: 30s # interval: 30s
timeout: 20s # timeout: 20s
retries: 3 # retries: 3
pgadmin: pgadmin:
image: dpage/pgadmin4 image: dpage/pgadmin4
ports: ports:
@ -54,15 +54,15 @@ services:
# - "host.docker.internal:host-gateway" # - "host.docker.internal:host-gateway"
# depends_on: # depends_on:
# - minio # - minio
tusd: # tusd:
image: tusproject/tusd # image: tusproject/tusd
ports: # ports:
- "8080:8080" # - "8080:8080"
command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook # command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook
volumes: # volumes:
- ./uploads:/data # - ./uploads:/data
extra_hosts: # extra_hosts:
- "host.docker.internal:host-gateway" # - "host.docker.internal:host-gateway"
nginx: nginx:
image: nice-nginx:latest image: nice-nginx:latest
ports: ports: