Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
4124dcb02c
|
@ -7,13 +7,118 @@ interface LayoutOptions {
|
|||
nodeSeparation?: number;
|
||||
}
|
||||
|
||||
|
||||
interface NodeWithLayout extends Node {
|
||||
width?: number;
|
||||
height?: number;
|
||||
children?: NodeWithLayout[];
|
||||
parent?: NodeWithLayout;
|
||||
subtreeHeight?: number;
|
||||
isRight?: boolean;
|
||||
}
|
||||
|
||||
export function getMindMapLayout(options: LayoutOptions) {
|
||||
return {
|
||||
nodes: options.nodes,
|
||||
edges: options.edges,
|
||||
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 {
|
||||
nodes: layoutedNodes,
|
||||
edges,
|
||||
};
|
||||
}
|
|
@ -116,9 +116,7 @@ export const GraphNode = memo(({ id, selected, data, isConnectable }: NodeProps<
|
|||
${isEditing ? 'ring-2 ring-white/50' : ''}
|
||||
`}
|
||||
>
|
||||
<div className={`p-2 ${baseTextStyles}`}>
|
||||
{data.label}
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
defaultValue={data.label}
|
||||
|
|
|
@ -10,28 +10,28 @@ services:
|
|||
- POSTGRES_PASSWORD=Letusdoit000
|
||||
volumes:
|
||||
- ./volumes/postgres:/var/lib/postgresql/data
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- ./volumes/minio:/minio_data
|
||||
environment:
|
||||
- MINIO_ACCESS_KEY=minioadmin
|
||||
- MINIO_SECRET_KEY=minioadmin
|
||||
command: minio server /minio_data --console-address ":9001" -address ":9000"
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"curl",
|
||||
"-f",
|
||||
"http://192.168.2.1:9001/minio/health/live"
|
||||
]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
# minio:
|
||||
# image: minio/minio
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "9001:9001"
|
||||
# volumes:
|
||||
# - ./volumes/minio:/minio_data
|
||||
# environment:
|
||||
# - MINIO_ACCESS_KEY=minioadmin
|
||||
# - MINIO_SECRET_KEY=minioadmin
|
||||
# command: minio server /minio_data --console-address ":9001" -address ":9000"
|
||||
# healthcheck:
|
||||
# test:
|
||||
# [
|
||||
# "CMD",
|
||||
# "curl",
|
||||
# "-f",
|
||||
# "http://192.168.2.1:9001/minio/health/live"
|
||||
# ]
|
||||
# interval: 30s
|
||||
# timeout: 20s
|
||||
# retries: 3
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4
|
||||
ports:
|
||||
|
@ -54,15 +54,15 @@ services:
|
|||
# - "host.docker.internal:host-gateway"
|
||||
# depends_on:
|
||||
# - minio
|
||||
tusd:
|
||||
image: tusproject/tusd
|
||||
ports:
|
||||
- "8080:8080"
|
||||
command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook
|
||||
volumes:
|
||||
- ./uploads:/data
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
# tusd:
|
||||
# image: tusproject/tusd
|
||||
# ports:
|
||||
# - "8080:8080"
|
||||
# command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook
|
||||
# volumes:
|
||||
# - ./uploads:/data
|
||||
# extra_hosts:
|
||||
# - "host.docker.internal:host-gateway"
|
||||
nginx:
|
||||
image: nice-nginx:latest
|
||||
ports:
|
||||
|
|
Loading…
Reference in New Issue