Reading & Doing
Old Tiny Games
这个问题想了很久才想出来一个近似完美(节点不会重叠)的解法(晚点上图):
回溯算法 + 碰撞检测
import { useEffect, useRef } from "react";
import { EdgeType, fakedata, NodeType, Reactiontree } from "./data";
import LogicFlow from "@logicflow/core";
import { SmilesNodeConfig, SmilesNodeConfigType } from "./BaseNode";
import { ProcessEdge, ProcessEdgeName } from "./BaseLine";
const ImageLength = 200;
const Length = 1200;
const Height = 700;
const NodeLength = 100;
const RowGap = 200;
const ColGap = 250;
const EdgeTypeValue = ProcessEdgeName;
let counter = 1;
const maxYofEachLevel: Record<number, number | undefined> = {};
const getChildYByParent = (parentNode: NodeType, childCount: number) => {
return childCount > 1
? parentNode.y - (NodeLength * childCount + RowGap * (childCount - 1)) / 2
: parentNode.y;
};
const generateNodeInfo = (
node: Reactiontree,
level: number,
parentNode: NodeType | null,
childCount: number
): NodeType => {
let y = 0;
let x = 0;
if (!Boolean(parentNode)) {
y = Height / 2 - NodeLength / 2;
x = Length - NodeLength - ColGap;
} else if (maxYofEachLevel[level] != undefined) {
const childYByParent = getChildYByParent(parentNode!, childCount);
let tmpY = maxYofEachLevel[level]! + 0;
// 碰撞检测
if (childYByParent < tmpY) {
parentNode!.y = parentNode!.y + (tmpY - childYByParent);
maxYofEachLevel[level - 1] = Math.max(
maxYofEachLevel[level - 1]!,
parentNode!.y
);
}
y = Math.max(tmpY + RowGap, childYByParent);
x = parentNode!.x - NodeLength - ColGap;
} else {
y = getChildYByParent(parentNode!, childCount);
x = parentNode!.x - NodeLength - ColGap;
}
return {
id: `${counter++}`,
y,
x,
type: SmilesNodeConfigType,
properties: {
smiles: node.smiles,
in_stock: node.in_stock,
imageLength: ImageLength,
},
};
};
const collectEdges = (
parentNode: NodeType,
childNode: NodeType,
edges: EdgeType[],
step: number
) => {
edges.push({
type: EdgeTypeValue,
id: `${childNode.id}->${parentNode.id}`,
sourceNodeId: childNode.id,
targetNodeId: parentNode.id,
text: step.toString(),
properties: {
ci: step,
targetId: parentNode.id,
},
});
};
const handleReactionTreePro = (tree: Reactiontree) => {
const nodes: NodeType[] = [];
const edges: EdgeType[] = [];
let step = 1;
const handle = (
mol: Reactiontree,
level = 1,
parentNode: null | NodeType = null
) => {
const childCount = Array.isArray(mol.children) ? mol.children.length : 0;
const node = generateNodeInfo(mol, level, parentNode, childCount);
nodes.push(node);
maxYofEachLevel[level] =
Math.max(
node.y,
Boolean(maxYofEachLevel[level]) ? maxYofEachLevel[level]! : -999
) + RowGap;
if (childCount > 0) {
// 用于记录箭头标号
let memoStep = step++;
// forEach作用在于回溯
mol.children!.forEach((childTree) => {
// 进行递归
const childNode = handle(childTree, level + 1, node);
collectEdges(node, childNode, edges, memoStep);
});
}
return node;
};
handle(tree);
return { nodes, edges };
};
export const ReverseTreeCanvas = () => {
const canvasRef = useRef(null);
const containerRef = useRef<null | HTMLDivElement>(null);
useEffect(() => {
if (!canvasRef.current) return;
const lf = new LogicFlow({
container: canvasRef.current,
stopScrollGraph: true,
stopZoomGraph: false,
width: Length,
height: Height,
textEdit: false, // 文本可编辑
isSilentMode: true, // 仅浏览不可编辑模式
hideAnchors: true,
grid: false,
hoverOutline: true,
});
// lf.register(SmilesNodeConfig);
lf.batchRegister([SmilesNodeConfig, ProcessEdge]);
const { nodes, edges } = handleReactionTreePro(fakedata);
// const { tailSmiles, reactions, stepCount, ...rest } =
// handleReactionTreePro(reactionTree);
// startTransition(() => {
// setReactions(reactions);
// setTailSmiles(tailSmiles);
// setStepCount(stepCount);
// });
// nodesAndEdgesRef.current = rest;
lf.render({ nodes, edges });
// lfRef.current = lf;
}, []);
return (
<>
<div ref={containerRef} className="mt-2 overflow-x-auto">
<p>reverse tree</p>
<div
ref={canvasRef}
style={{ width: `${Length}px`, height: `${Height}px` }}
id="dag-canvas"
></div>
</div>
</>
);
};