Skip to content

Commit 11759af

Browse files
committed
fix: vertical flow support
1 parent 7175132 commit 11759af

File tree

5 files changed

+222
-167
lines changed

5 files changed

+222
-167
lines changed

src/app/app.component.ts

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,61 @@
11
import { Component, ViewChild, inject } from '@angular/core';
22
import { NgForOf } from '@angular/common';
33
import { RouterOutlet } from '@angular/router';
4+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
45
import { FlowChildComponent } from './flow/flow-child.component';
56
import { FlowComponent } from './flow/flow.component';
67
import { FlowOptions } from './flow/flow-interface';
78

89
@Component({
910
selector: 'app-root',
1011
standalone: true,
11-
imports: [NgForOf, RouterOutlet, FlowComponent, FlowChildComponent],
12+
imports: [
13+
NgForOf,
14+
RouterOutlet,
15+
ReactiveFormsModule,
16+
FlowComponent,
17+
FlowChildComponent,
18+
],
1219
template: `
13-
<button (click)="trigger()">Arrange</button>
14-
15-
<label for="zoomingId">
16-
<input
17-
id="zoomingId"
18-
type="checkbox"
19-
[checked]="zooming"
20-
(click)="zoomingFn()"
21-
/>zooming
22-
</label>
23-
<label for="childDraggingId">
24-
<input
25-
id="childDraggingId"
26-
type="checkbox"
27-
[checked]="childDragging"
28-
(click)="childDraggingFn()"
29-
/>Child Dragging
30-
</label>
31-
<button (click)="fitToWindow()">Fit to window</button>
20+
<div class="flex items-center gap-3">
21+
<button (click)="trigger()">Arrange</button>
22+
<label for="zoomingId">
23+
<input
24+
id="zoomingId"
25+
type="checkbox"
26+
[checked]="zooming"
27+
(click)="zoomingFn()"
28+
/>zooming
29+
</label>
30+
<label for="childDraggingId">
31+
<input
32+
id="childDraggingId"
33+
type="checkbox"
34+
[checked]="childDragging"
35+
(click)="childDraggingFn()"
36+
/>Child Dragging
37+
</label>
38+
<button (click)="fitToWindow()">Fit to window</button>
39+
40+
<!-- radio group for horizontal or vertical -->
41+
<label for="direction">
42+
<input
43+
id="direction"
44+
type="radio"
45+
[value]="'horizontal'"
46+
[formControl]="direction"
47+
/>Horizontal
48+
</label>
49+
<label for="direction1">
50+
<input
51+
id="direction1"
52+
type="radio"
53+
[value]="'vertical'"
54+
[formControl]="direction"
55+
/>Vertical
56+
</label>
57+
</div>
58+
3259
<div class="flex items-center justify-center h-[700px]">
3360
<app-flow class="max-w-[90%] max-h-[90%] border">
3461
<div
@@ -83,6 +110,7 @@ export class AppComponent {
83110
list: FlowOptions[] = [];
84111
zooming = true;
85112
childDragging = true;
113+
direction = new FormControl<'horizontal' | 'vertical'>('horizontal');
86114
linkingFrom: number | null = null; // Store the index of the node that we start linking from
87115
@ViewChild(FlowComponent) flowComponent: FlowComponent;
88116

src/app/flow/arrangements.ts

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,55 +13,22 @@ export class Arrangements {
1313
public autoArrange(): Map<string, FlowOptions> {
1414
const newItems = new Map<string, FlowOptions>();
1515
let currentX = 0;
16+
let currentY = 0;
1617

17-
if (this.direction === 'horizontal') {
18-
// Start by positioning the base nodes
19-
const baseNodes = this.list.filter(
20-
(node) => node.position.deps.length === 0
21-
);
22-
23-
let level = 0;
18+
// Handle both horizontal and vertical directions
19+
const baseNodes = this.list.filter(
20+
(node) => node.position.deps.length === 0
21+
);
2422

25-
for (const baseNode of baseNodes) {
26-
const consumedHeight = this.positionDependents(
27-
baseNode,
28-
0,
29-
0,
30-
newItems
31-
);
32-
// const centerY = consumedHeight / 2;
33-
// newItems.set(baseNode.position.id, {
34-
// ...baseNode.position,
35-
// x: currentX,
36-
// y: centerY - baseNode.elRect.height / 2,
37-
// });
23+
for (const baseNode of baseNodes) {
24+
if (this.direction === 'horizontal') {
25+
this.positionDependents(baseNode, currentX, 0, newItems);
3826
currentX += baseNode.elRect.width + this.horizontalPadding;
27+
} else {
28+
// Vertical arrangement
29+
this.positionDependents(baseNode, 0, currentY, newItems);
30+
currentY += baseNode.elRect.height + this.horizontalPadding;
3931
}
40-
} else {
41-
// direction === 'vertical'
42-
// let currentY = 0;
43-
// for (const level in levelsMap) {
44-
// const itemsInLevel = levelsMap[level];
45-
// let currentX = 0;
46-
// // Sort items within the level by their current x position
47-
// itemsInLevel.sort((a, b) => a.position.x - b.position.x);
48-
// for (const node of itemsInLevel) {
49-
// const newNode: FlowOptions = {
50-
// ...node.position,
51-
// x: currentX,
52-
// y: currentY,
53-
// };
54-
// currentX += node.elRect.width + this.horizontalPadding;
55-
// newItems.set(node.position.id, newNode);
56-
// }
57-
// const maxHeightItem = itemsInLevel.reduce((max, item) => {
58-
// return item.elRect.height > max.elRect.height ? item : max;
59-
// }, itemsInLevel[0]);
60-
// currentY +=
61-
// maxHeightItem.elRect.height +
62-
// this.verticalPadding +
63-
// this.groupPadding;
64-
// }
6532
}
6633

6734
return newItems;
@@ -77,14 +44,17 @@ export class Arrangements {
7744
gp: -this.groupPadding * 2,
7845
maxDepLength: 0,
7946
}
80-
): { consumedHeight: number; dep: boolean } {
47+
): { consumedSpace: number; dep: boolean } {
8148
const dependents = this.list.filter((child) =>
8249
child.position.deps.includes(baseNode.position.id)
8350
);
8451

52+
const isV = this.direction === 'vertical';
53+
8554
let startY = baseY;
86-
let newX = baseX + baseNode.elRect.width + this.horizontalPadding;
87-
const height = baseNode.elRect.height;
55+
const { width: w, height: h } = baseNode.elRect;
56+
let newX = baseX + (isV ? h : w) + this.horizontalPadding;
57+
const height = isV ? w : h;
8858

8959
const childC: { first: boolean; gp: number; maxDepLength: number } = {
9060
first: true,
@@ -95,7 +65,7 @@ export class Arrangements {
9565
const depLast = i === dependents.length - 1;
9666
childC.first = i === 0;
9767
const dependent = dependents[i];
98-
const { consumedHeight, dep } = this.positionDependents(
68+
const { consumedSpace, dep } = this.positionDependents(
9969
dependent,
10070
newX,
10171
startY,
@@ -109,7 +79,7 @@ export class Arrangements {
10979
startY += this.groupPadding;
11080
config.gp += this.groupPadding;
11181
}
112-
startY += consumedHeight + (!depLast ? this.verticalPadding : 0);
82+
startY += consumedSpace + (!depLast ? this.verticalPadding : 0);
11383
}
11484

11585
// baseY += childC.gp;
@@ -118,12 +88,12 @@ export class Arrangements {
11888
let y = 0;
11989
if (dependents.length > 1) {
12090
// find the first and last dependent and there y position
121-
const firstDep = dependents[0];
122-
const lastDep = dependents[dependents.length - 1];
123-
const firstDepY = newItems.get(firstDep.position.id)!.y;
124-
const lastDepY = newItems.get(lastDep.position.id)!.y;
91+
const firstDepId = dependents[0].position.id;
92+
const lastDepId = dependents[dependents.length - 1].position.id;
93+
const firstDep = newItems.get(firstDepId)!;
94+
const lastDep = newItems.get(lastDepId)!;
12595
// find the center of the first and last dependent
126-
y = (firstDepY + lastDepY) / 2;
96+
y = (isV ? firstDep.x + lastDep.x : firstDep.y + lastDep.y) / 2;
12797
} else {
12898
y = baseY + (dependents.length ? (startY - baseY) / 2 - height / 2 : 0);
12999

@@ -136,13 +106,13 @@ export class Arrangements {
136106
}
137107
newItems.set(baseNode.position.id, {
138108
...baseNode.position,
139-
x: baseX,
140-
y: y,
109+
x: isV ? y : baseX,
110+
y: isV ? baseX : y,
141111
});
142112
// add groupPadding if there are more than one dependency
143113
const groupPad =
144114
dependents.length > 1 ? this.groupPadding - this.verticalPadding : 0;
145-
const consumedHeight = startY + (dependents.length ? 0 : height) + groupPad;
146-
return { consumedHeight, dep: dependents.length > 0 };
115+
const consumedSpace = startY + (dependents.length ? 0 : height) + groupPad;
116+
return { consumedSpace, dep: dependents.length > 0 };
147117
}
148118
}

src/app/flow/connections.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ export class Connections {
1010
// value = index of the closest dot
1111
closestDots = new Map<string, number>();
1212

13-
constructor(private list: ChildInfo[]) {
13+
constructor(
14+
private list: ChildInfo[],
15+
private direction: 'horizontal' | 'vertical' = 'horizontal'
16+
) {
1417
this.setReverseDepsMap(list.map((x) => x.position));
1518
// console.count('Connections');
1619
}
@@ -77,6 +80,7 @@ export class Connections {
7780
// sides dot index order: [top, right, bottom, left]
7881
const thresholdDistance = 10; // Example distance threshold. Adjust as needed.
7982
let swapped = false;
83+
const isV = this.direction === 'vertical';
8084
// correct the parent based on the deps
8185
if (!child.position.deps.includes(parent.position.id)) {
8286
const _t = child;
@@ -91,10 +95,17 @@ export class Connections {
9195
const { x, y } = child.position;
9296
const { x: px, y: py } = parent.position;
9397

94-
if (x + width < px) return 'left';
95-
if (x - width > px) return 'right';
96-
if (y + height < py) return 'top';
97-
if (y - height > py) return 'bottom';
98+
if (!isV) {
99+
if (x + width < px) return 'left';
100+
if (x - width > px) return 'right';
101+
if (y + height < py) return 'top';
102+
if (y - height > py) return 'bottom';
103+
} else {
104+
if (y + height < py) return 'top';
105+
if (y - height > py) return 'bottom';
106+
if (x + width < px) return 'left';
107+
if (x - width > px) return 'right';
108+
}
98109
return 'right';
99110
})();
100111

0 commit comments

Comments
 (0)