mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-29 05:06:43 +02:00
#6646 Tidy-tree layout in place for Mindmaps
This commit is contained in:
@@ -105,7 +105,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<pre id="diagram4" class="mermaid">
|
<pre id="diagram4" class="mermaid2">
|
||||||
---
|
---
|
||||||
config:
|
config:
|
||||||
layout: tidy-tree
|
layout: tidy-tree
|
||||||
@@ -135,31 +135,25 @@
|
|||||||
layout: tidy-tree
|
layout: tidy-tree
|
||||||
---
|
---
|
||||||
mindmap
|
mindmap
|
||||||
root((mindmap))
|
root((mindmap is a long thing))
|
||||||
A
|
A
|
||||||
|
|
||||||
B
|
B
|
||||||
a
|
|
||||||
b
|
|
||||||
c
|
|
||||||
d
|
|
||||||
a1
|
|
||||||
a2
|
|
||||||
C
|
C
|
||||||
e
|
|
||||||
f
|
|
||||||
g
|
|
||||||
h
|
|
||||||
i
|
|
||||||
D
|
D
|
||||||
q1
|
</pre
|
||||||
q2
|
>
|
||||||
I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on
|
<pre id="diagram4" class="mermaid">
|
||||||
q3
|
---
|
||||||
q4
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
</pre>
|
---
|
||||||
<pre id="diagram4" class="mermaid2">
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
A
|
||||||
|
B
|
||||||
|
</pre
|
||||||
|
>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
---
|
---
|
||||||
config:
|
config:
|
||||||
layout: tidy-tree
|
layout: tidy-tree
|
||||||
@@ -167,22 +161,37 @@
|
|||||||
mindmap
|
mindmap
|
||||||
root((mindmap))
|
root((mindmap))
|
||||||
A
|
A
|
||||||
Origins
|
|
||||||
Tools
|
|
||||||
e
|
|
||||||
Third
|
|
||||||
q
|
|
||||||
w
|
|
||||||
e
|
|
||||||
r
|
|
||||||
t
|
|
||||||
y
|
|
||||||
Forth
|
|
||||||
a
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
b
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
B
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
D
|
||||||
|
apa5[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
</pre>
|
</pre>
|
||||||
<pre id="diagram4" class="mermaid2">
|
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
layout: tidy-tree
|
||||||
|
---
|
||||||
|
mindmap
|
||||||
|
root((mindmap))
|
||||||
|
a
|
||||||
|
apa[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa2[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
b
|
||||||
|
apa3[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
apa4[I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on. I am a long long multline string passing several levels of text. Lorum ipsum and so on]
|
||||||
|
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
<pre id="diagram4" class="mermaid">
|
||||||
---
|
---
|
||||||
config:
|
config:
|
||||||
layout: tidy-tree
|
layout: tidy-tree
|
||||||
@@ -210,7 +219,7 @@
|
|||||||
layout: dagre
|
layout: dagre
|
||||||
---
|
---
|
||||||
mindmap
|
mindmap
|
||||||
root((mindmap))
|
root((mindmap is a long thing))
|
||||||
Origins
|
Origins
|
||||||
Long history
|
Long history
|
||||||
::icon(fa fa-book)
|
::icon(fa fa-book)
|
||||||
|
@@ -29,7 +29,7 @@ export const render = async (
|
|||||||
log,
|
log,
|
||||||
positionEdgeLabel,
|
positionEdgeLabel,
|
||||||
}: InternalHelpers,
|
}: InternalHelpers,
|
||||||
{ algorithm }: RenderOptions
|
{ algorithm: _algorithm }: RenderOptions
|
||||||
) => {
|
) => {
|
||||||
const nodeDb: Record<string, NodeWithPosition> = {};
|
const nodeDb: Record<string, NodeWithPosition> = {};
|
||||||
const clusterDb: Record<string, any> = {};
|
const clusterDb: Record<string, any> = {};
|
||||||
@@ -121,7 +121,7 @@ export const render = async (
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
data4Layout.edges.map(async (edge) => {
|
data4Layout.edges.map(async (edge) => {
|
||||||
// Insert edge label first
|
// Insert edge label first
|
||||||
const edgeLabel = await insertEdgeLabel(edgeLabels, edge);
|
const _edgeLabel = await insertEdgeLabel(edgeLabels, edge);
|
||||||
|
|
||||||
// Get start and end nodes
|
// Get start and end nodes
|
||||||
const startNode = nodeDb[edge.start];
|
const startNode = nodeDb[edge.start];
|
||||||
@@ -132,7 +132,7 @@ export const render = async (
|
|||||||
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
||||||
|
|
||||||
if (positionedEdge) {
|
if (positionedEdge) {
|
||||||
console.debug('APA01 positionedEdge', positionedEdge);
|
log.debug('APA01 positionedEdge', positionedEdge);
|
||||||
// Create edge path with positioned coordinates
|
// Create edge path with positioned coordinates
|
||||||
const edgeWithPath = {
|
const edgeWithPath = {
|
||||||
...edge,
|
...edge,
|
||||||
|
@@ -9,7 +9,7 @@ vi.mock('non-layered-tidy-tree-layout', () => ({
|
|||||||
const result = { ...treeData };
|
const result = { ...treeData };
|
||||||
|
|
||||||
// Set positions for the virtual root (if it exists)
|
// Set positions for the virtual root (if it exists)
|
||||||
if (result.id && result.id.toString().startsWith('virtual-root')) {
|
if (result.id?.toString().startsWith('virtual-root')) {
|
||||||
result.x = 0;
|
result.x = 0;
|
||||||
result.y = 0;
|
result.y = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -322,5 +322,109 @@ describe('Tidy-Tree Layout Algorithm', () => {
|
|||||||
expect(child2!.x).toBeGreaterThan(100); // Should be significantly right of center
|
expect(child2!.x).toBeGreaterThan(100); // Should be significantly right of center
|
||||||
expect(child4!.x).toBeGreaterThan(100); // Should be significantly right of center
|
expect(child4!.x).toBeGreaterThan(100); // Should be significantly right of center
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should correctly transpose coordinates to prevent high nodes from covering nodes above them', async () => {
|
||||||
|
// Create a test case with nodes that have different heights to test transposition
|
||||||
|
const testData = {
|
||||||
|
...mockLayoutData,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'root',
|
||||||
|
label: 'Root',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect' as const,
|
||||||
|
width: 100,
|
||||||
|
height: 50,
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tall-child',
|
||||||
|
label: 'Tall Child',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect' as const,
|
||||||
|
width: 80,
|
||||||
|
height: 120, // Tall node
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'short-child',
|
||||||
|
label: 'Short Child',
|
||||||
|
isGroup: false,
|
||||||
|
shape: 'rect' as const,
|
||||||
|
width: 80,
|
||||||
|
height: 30, // Short node
|
||||||
|
padding: 10,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
cssClasses: '',
|
||||||
|
cssStyles: [],
|
||||||
|
look: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
id: 'root_tall',
|
||||||
|
start: 'root',
|
||||||
|
end: 'tall-child',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'root_short',
|
||||||
|
start: 'root',
|
||||||
|
end: 'short-child',
|
||||||
|
type: 'edge',
|
||||||
|
classes: '',
|
||||||
|
style: [],
|
||||||
|
animate: false,
|
||||||
|
arrowTypeEnd: 'arrow_point',
|
||||||
|
arrowTypeStart: 'none',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await executeTidyTreeLayout(testData, mockConfig);
|
||||||
|
|
||||||
|
expect(result).toBeDefined();
|
||||||
|
expect(result.nodes).toHaveLength(3); // root + 2 children
|
||||||
|
|
||||||
|
// Find all nodes
|
||||||
|
const rootNode = result.nodes.find((node) => node.id === 'root');
|
||||||
|
const tallChild = result.nodes.find((node) => node.id === 'tall-child');
|
||||||
|
const shortChild = result.nodes.find((node) => node.id === 'short-child');
|
||||||
|
|
||||||
|
expect(rootNode).toBeDefined();
|
||||||
|
expect(tallChild).toBeDefined();
|
||||||
|
expect(shortChild).toBeDefined();
|
||||||
|
|
||||||
|
// Verify that nodes are positioned correctly with proper transposition
|
||||||
|
// The tall child and short child should be on opposite sides
|
||||||
|
expect(tallChild!.x).not.toBe(shortChild!.x); // Should be on different sides
|
||||||
|
|
||||||
|
// Verify that the original dimensions are preserved (not transposed in final output)
|
||||||
|
expect(tallChild!.width).toBe(80); // Original width preserved
|
||||||
|
expect(tallChild!.height).toBe(120); // Original height preserved
|
||||||
|
expect(shortChild!.width).toBe(80); // Original width preserved
|
||||||
|
expect(shortChild!.height).toBe(30); // Original height preserved
|
||||||
|
|
||||||
|
// Verify that nodes don't overlap vertically (the transposition fix)
|
||||||
|
// Both children should have reasonable Y positions that don't cause overlap
|
||||||
|
const yDifference = Math.abs(tallChild!.y - shortChild!.y);
|
||||||
|
expect(yDifference).toBeGreaterThanOrEqual(0); // Should have proper vertical spacing
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -19,7 +19,7 @@ export function executeTidyTreeLayout(
|
|||||||
data: LayoutData,
|
data: LayoutData,
|
||||||
_config: MermaidConfig
|
_config: MermaidConfig
|
||||||
): Promise<LayoutResult> {
|
): Promise<LayoutResult> {
|
||||||
log.debug('Starting tidy-tree layout algorithm');
|
log.debug('APA01 Starting tidy-tree layout algorithm');
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
@@ -33,13 +33,15 @@ export function executeTidyTreeLayout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find the maximum number of children any one node have expect for the root node
|
// Find the maximum number of children any one node have expect for the root node
|
||||||
const maxChildren = Math.max(...data.nodes.map((node) => node.children?.length ?? 0));
|
const _maxChildren = Math.max(...data.nodes.map((node) => node.children?.length ?? 0));
|
||||||
|
|
||||||
// Convert layout data to dual-tree format (left and right trees)
|
// Convert layout data to dual-tree format (left and right trees)
|
||||||
const { leftTree, rightTree, rootNode } = convertToDualTreeFormat(data);
|
const { leftTree, rightTree, rootNode } = convertToDualTreeFormat(data);
|
||||||
|
|
||||||
// Configure tidy-tree layout
|
// Configure tidy-tree layout with dynamic gap based on node heights
|
||||||
const gap = 20; // Vertical gap between nodes
|
// Since we transpose coordinates, the gap becomes horizontal spacing in final layout
|
||||||
|
// We need to ensure enough space for the tallest nodes
|
||||||
|
const gap = 20; // Math.max(20, maxNodeHeight + 20); // Ensure minimum gap plus node height
|
||||||
const bottomPadding = 40; // Horizontal gap between levels
|
const bottomPadding = 40; // Horizontal gap between levels
|
||||||
intersectionShift = 30;
|
intersectionShift = 30;
|
||||||
|
|
||||||
@@ -58,8 +60,10 @@ export function executeTidyTreeLayout(
|
|||||||
let rightBoundingBox = null;
|
let rightBoundingBox = null;
|
||||||
|
|
||||||
if (leftTree) {
|
if (leftTree) {
|
||||||
|
// log.debug('APA01 Left tree before layout', leftTree.children[0]?.children[0]);
|
||||||
const leftLayoutResult = layout.layout(leftTree);
|
const leftLayoutResult = layout.layout(leftTree);
|
||||||
leftResult = leftLayoutResult.result;
|
leftResult = leftLayoutResult.result;
|
||||||
|
log.debug('APA01 Left tree before layout', JSON.stringify(leftResult, null, 2));
|
||||||
leftBoundingBox = leftLayoutResult.boundingBox;
|
leftBoundingBox = leftLayoutResult.boundingBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,11 +132,13 @@ function convertToDualTreeFormat(data: LayoutData): {
|
|||||||
const parentId = edge.start;
|
const parentId = edge.start;
|
||||||
const childId = edge.end;
|
const childId = edge.end;
|
||||||
|
|
||||||
if (!children.has(parentId)) {
|
if (parentId && childId) {
|
||||||
children.set(parentId, []);
|
if (!children.has(parentId)) {
|
||||||
|
children.set(parentId, []);
|
||||||
|
}
|
||||||
|
children.get(parentId)!.push(childId);
|
||||||
|
parents.set(childId, parentId);
|
||||||
}
|
}
|
||||||
children.get(parentId)!.push(childId);
|
|
||||||
parents.set(childId, parentId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Find root node (node with no parent)
|
// Find root node (node with no parent)
|
||||||
@@ -241,7 +247,7 @@ function combineAndPositionTrees(
|
|||||||
): PositionedNode[] {
|
): PositionedNode[] {
|
||||||
const positionedNodes: PositionedNode[] = [];
|
const positionedNodes: PositionedNode[] = [];
|
||||||
|
|
||||||
console.log('combineAndPositionTrees', {
|
log.debug('combineAndPositionTrees', {
|
||||||
leftResult,
|
leftResult,
|
||||||
rightResult,
|
rightResult,
|
||||||
});
|
});
|
||||||
@@ -251,7 +257,7 @@ function combineAndPositionTrees(
|
|||||||
const rootY = 0;
|
const rootY = 0;
|
||||||
|
|
||||||
// Calculate spacing between trees
|
// Calculate spacing between trees
|
||||||
const treeSpacing = 150; // Horizontal spacing from root to tree
|
const treeSpacing = rootNode.width / 2 + 30; // Horizontal spacing from root to tree
|
||||||
|
|
||||||
// First, collect node positions for each tree separately
|
// First, collect node positions for each tree separately
|
||||||
const leftTreeNodes: PositionedNode[] = [];
|
const leftTreeNodes: PositionedNode[] = [];
|
||||||
@@ -287,8 +293,13 @@ function combineAndPositionTrees(
|
|||||||
const firstLevelLeftNodes = leftTreeNodes.filter((node) => node.x === firstLevelLeftX);
|
const firstLevelLeftNodes = leftTreeNodes.filter((node) => node.x === firstLevelLeftX);
|
||||||
|
|
||||||
if (firstLevelLeftNodes.length > 0) {
|
if (firstLevelLeftNodes.length > 0) {
|
||||||
const leftMinY = Math.min(...firstLevelLeftNodes.map((node) => node.y));
|
// Calculate bounding box considering node heights
|
||||||
const leftMaxY = Math.max(...firstLevelLeftNodes.map((node) => node.y));
|
const leftMinY = Math.min(
|
||||||
|
...firstLevelLeftNodes.map((node) => node.y - (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
|
const leftMaxY = Math.max(
|
||||||
|
...firstLevelLeftNodes.map((node) => node.y + (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
leftTreeCenterY = (leftMinY + leftMaxY) / 2;
|
leftTreeCenterY = (leftMinY + leftMaxY) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,8 +314,13 @@ function combineAndPositionTrees(
|
|||||||
const firstLevelRightNodes = rightTreeNodes.filter((node) => node.x === firstLevelRightX);
|
const firstLevelRightNodes = rightTreeNodes.filter((node) => node.x === firstLevelRightX);
|
||||||
|
|
||||||
if (firstLevelRightNodes.length > 0) {
|
if (firstLevelRightNodes.length > 0) {
|
||||||
const rightMinY = Math.min(...firstLevelRightNodes.map((node) => node.y));
|
// Calculate bounding box considering node heights
|
||||||
const rightMaxY = Math.max(...firstLevelRightNodes.map((node) => node.y));
|
const rightMinY = Math.min(
|
||||||
|
...firstLevelRightNodes.map((node) => node.y - (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
|
const rightMaxY = Math.max(
|
||||||
|
...firstLevelRightNodes.map((node) => node.y + (node.height ?? 50) / 2)
|
||||||
|
);
|
||||||
rightTreeCenterY = (rightMinY + rightMaxY) / 2;
|
rightTreeCenterY = (rightMinY + rightMaxY) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +333,7 @@ function combineAndPositionTrees(
|
|||||||
positionedNodes.push({
|
positionedNodes.push({
|
||||||
id: String(rootNode.id),
|
id: String(rootNode.id),
|
||||||
x: rootX,
|
x: rootX,
|
||||||
y: rootY, // Root stays at center
|
y: rootY + 20, // Root stays at center
|
||||||
section: 'root',
|
section: 'root',
|
||||||
width: rootNode._originalNode?.width || rootNode.width,
|
width: rootNode._originalNode?.width || rootNode.width,
|
||||||
height: rootNode._originalNode?.height || rootNode.height,
|
height: rootNode._originalNode?.height || rootNode.height,
|
||||||
@@ -325,30 +341,31 @@ function combineAndPositionTrees(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Add left tree nodes with their specific offset
|
// Add left tree nodes with their specific offset
|
||||||
leftTreeNodes.forEach((node) => {
|
const leftTreeNodesWithOffset = leftTreeNodes.map((node) => ({
|
||||||
positionedNodes.push({
|
id: node.id,
|
||||||
id: node.id,
|
x: node.x - node.width / 2,
|
||||||
x: node.x,
|
y: node.y + leftTreeOffset + node.height / 2,
|
||||||
y: node.y + leftTreeOffset,
|
section: 'left' as const,
|
||||||
section: 'left',
|
width: node.width,
|
||||||
width: node.width,
|
height: node.height,
|
||||||
height: node.height,
|
originalNode: node.originalNode,
|
||||||
originalNode: node.originalNode,
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add right tree nodes with their specific offset
|
// Add right tree nodes with their specific offset
|
||||||
rightTreeNodes.forEach((node) => {
|
const rightTreeNodesWithOffset = rightTreeNodes.map((node) => ({
|
||||||
positionedNodes.push({
|
id: node.id,
|
||||||
id: node.id,
|
x: node.x + node.width / 2,
|
||||||
x: node.x,
|
y: node.y + rightTreeOffset + node.height / 2,
|
||||||
y: node.y + rightTreeOffset,
|
section: 'right' as const,
|
||||||
section: 'right',
|
width: node.width,
|
||||||
width: node.width,
|
height: node.height,
|
||||||
height: node.height,
|
originalNode: node.originalNode,
|
||||||
originalNode: node.originalNode,
|
}));
|
||||||
});
|
|
||||||
});
|
// Add all nodes to the final result
|
||||||
|
// The tidy-tree algorithm should handle proper spacing, so no additional collision detection needed
|
||||||
|
positionedNodes.push(...leftTreeNodesWithOffset);
|
||||||
|
positionedNodes.push(...rightTreeNodesWithOffset);
|
||||||
|
|
||||||
return positionedNodes;
|
return positionedNodes;
|
||||||
}
|
}
|
||||||
@@ -364,18 +381,25 @@ function positionLeftTreeBidirectional(
|
|||||||
offsetY: number
|
offsetY: number
|
||||||
): void {
|
): void {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
// For left tree: transpose the tidy-tree coordinates
|
// For left tree: transpose the tidy-tree coordinates correctly
|
||||||
// Tidy-tree Y becomes our X distance from root (grows left)
|
// Tidy-tree X (tree levels going down) becomes our Y position (vertical spacing)
|
||||||
// Tidy-tree X becomes our Y position (tree levels) - this gives proper spacing
|
// Tidy-tree Y (sibling spacing) becomes our X distance from root (grows left)
|
||||||
const distanceFromRoot = node.y ?? 0; // How far left from root
|
const distanceFromRoot = node.y ?? 0; // Horizontal distance from root (grows left)
|
||||||
const treeLevel = node.x ?? 0; // Use X coordinate for tree level (proper vertical spacing)
|
const verticalPosition = node.x ?? 0; // Vertical position (tree levels)
|
||||||
|
|
||||||
|
// Get the original node dimensions directly from the stored original node
|
||||||
|
const originalWidth = node._originalNode?.width ?? 100;
|
||||||
|
const originalHeight = node._originalNode?.height ?? 50;
|
||||||
|
|
||||||
|
// Use the vertical position as calculated by the tidy-tree algorithm
|
||||||
|
const adjustedY = offsetY + verticalPosition;
|
||||||
|
|
||||||
positionedNodes.push({
|
positionedNodes.push({
|
||||||
id: String(node.id),
|
id: String(node.id),
|
||||||
x: offsetX - distanceFromRoot, // Negative to grow left from root
|
x: offsetX - distanceFromRoot, // Negative to grow left from root
|
||||||
y: offsetY + treeLevel, // Use tidy-tree's Y as Y (tree levels)
|
y: adjustedY, // Vertical position based on tree level
|
||||||
width: node._originalNode?.width || node.width,
|
width: originalWidth,
|
||||||
height: node._originalNode?.height || node.height,
|
height: originalHeight,
|
||||||
originalNode: node._originalNode,
|
originalNode: node._originalNode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -396,18 +420,25 @@ function positionRightTreeBidirectional(
|
|||||||
offsetY: number
|
offsetY: number
|
||||||
): void {
|
): void {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
// For right tree: transpose the tidy-tree coordinates
|
// For right tree: transpose the tidy-tree coordinates correctly
|
||||||
// Tidy-tree Y becomes our X distance from root (grows right)
|
// Tidy-tree X (tree levels going down) becomes our Y position (vertical spacing)
|
||||||
// Tidy-tree X becomes our Y position (tree levels) - this gives proper spacing
|
// Tidy-tree Y (sibling spacing) becomes our X distance from root (grows right)
|
||||||
const distanceFromRoot = node.y ?? 0; // How far right from root
|
const distanceFromRoot = node.y ?? 0; // Horizontal distance from root (grows right)
|
||||||
const treeLevel = node.x ?? 0; // Use X coordinate for tree level (proper vertical spacing)
|
const verticalPosition = node.x ?? 0; // Vertical position (tree levels)
|
||||||
|
|
||||||
|
// Get the original node dimensions directly from the stored original node
|
||||||
|
const originalWidth = node._originalNode?.width ?? 100;
|
||||||
|
const originalHeight = node._originalNode?.height ?? 50;
|
||||||
|
|
||||||
|
// Use the vertical position as calculated by the tidy-tree algorithm
|
||||||
|
const adjustedY = offsetY + verticalPosition;
|
||||||
|
|
||||||
positionedNodes.push({
|
positionedNodes.push({
|
||||||
id: String(node.id),
|
id: String(node.id),
|
||||||
x: offsetX + distanceFromRoot, // Positive to grow right from root
|
x: offsetX + distanceFromRoot, // Positive to grow right from root
|
||||||
y: offsetY + treeLevel, // Use tidy-tree's Y as Y (tree levels)
|
y: adjustedY, // Vertical position based on tree level
|
||||||
width: node._originalNode?.width || node.width,
|
width: originalWidth,
|
||||||
height: node._originalNode?.height || node.height,
|
height: originalHeight,
|
||||||
originalNode: node._originalNode,
|
originalNode: node._originalNode,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -434,15 +465,51 @@ function calculateEdgePositions(
|
|||||||
return edges.map((edge) => {
|
return edges.map((edge) => {
|
||||||
const sourceNode = nodeInfo.get(edge.start ?? '');
|
const sourceNode = nodeInfo.get(edge.start ?? '');
|
||||||
const targetNode = nodeInfo.get(edge.end ?? '');
|
const targetNode = nodeInfo.get(edge.end ?? '');
|
||||||
console.debug('APA01 calculateEdgePositions', edge, sourceNode, 'targetNode', targetNode);
|
log.debug('APA01 calculateEdgePositions', edge, sourceNode, 'targetNode', targetNode);
|
||||||
|
|
||||||
if (!sourceNode) {
|
if (!sourceNode) {
|
||||||
console.error('APA01 Source node not found for edge', edge);
|
log.error('APA01 Source node not found for edge', edge);
|
||||||
return;
|
// Return a default edge instead of undefined
|
||||||
|
return {
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.start ?? '',
|
||||||
|
target: edge.end ?? '',
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
midX: 0,
|
||||||
|
midY: 0,
|
||||||
|
endX: 0,
|
||||||
|
endY: 0,
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
sourceSection: undefined,
|
||||||
|
targetSection: undefined,
|
||||||
|
sourceWidth: undefined,
|
||||||
|
sourceHeight: undefined,
|
||||||
|
targetWidth: undefined,
|
||||||
|
targetHeight: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if (targetNode === undefined) {
|
if (targetNode === undefined) {
|
||||||
console.error('APA01 Target node not found for edge', edge);
|
log.error('APA01 Target node not found for edge', edge);
|
||||||
return;
|
// Return a default edge instead of undefined
|
||||||
|
return {
|
||||||
|
id: edge.id,
|
||||||
|
source: edge.start ?? '',
|
||||||
|
target: edge.end ?? '',
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
midX: 0,
|
||||||
|
midY: 0,
|
||||||
|
endX: 0,
|
||||||
|
endY: 0,
|
||||||
|
points: [{ x: 0, y: 0 }],
|
||||||
|
sourceSection: undefined,
|
||||||
|
targetSection: undefined,
|
||||||
|
sourceWidth: undefined,
|
||||||
|
sourceHeight: undefined,
|
||||||
|
targetWidth: undefined,
|
||||||
|
targetHeight: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback positions if nodes not found
|
// Fallback positions if nodes not found
|
||||||
|
@@ -152,7 +152,7 @@ export const render = async (
|
|||||||
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
const positionedEdge = layoutResult.edges.find((e) => e.id === edge.id);
|
||||||
|
|
||||||
if (positionedEdge) {
|
if (positionedEdge) {
|
||||||
console.debug('APA01 positionedEdge', positionedEdge);
|
log.debug('APA01 positionedEdge', positionedEdge);
|
||||||
// Create edge path with positioned coordinates
|
// Create edge path with positioned coordinates
|
||||||
const edgeWithPath = {
|
const edgeWithPath = {
|
||||||
...edge,
|
...edge,
|
||||||
|
Reference in New Issue
Block a user