Skip to content

Commit

Permalink
refactor: add new auto arrangement method and improved demo changes (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheikalthaf authored Jan 30, 2024
1 parent 1076ea5 commit 9ec802b
Show file tree
Hide file tree
Showing 17 changed files with 590 additions and 388 deletions.
2 changes: 1 addition & 1 deletion projects/flow/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Angular Flow

Angular Flow is a component that allows you to create a flow diagram using Angular.
Live Demo [link](https://sheikalthaf.github.io/flow/)
Live Demo [link](https://uiuniversal.github.io/flow/)

Stackblitz Demo [link](https://stackblitz.com/edit/ngu-flow)

Expand Down
2 changes: 1 addition & 1 deletion projects/flow/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ngu/flow",
"version": "0.0.3",
"version": "0.0.4",
"peerDependencies": {
"@angular/common": "^14.0.0",
"@angular/core": "^14.0.0"
Expand Down
29 changes: 28 additions & 1 deletion projects/flow/src/lib/arrangements.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Arrangements } from './arrangements';
import { Arrangements, Arrangements2 } from './arrangements';
import { ChildInfo } from './flow-interface';

export const FLOW_LIST = [
Expand Down Expand Up @@ -38,3 +38,30 @@ describe('Arrangements', () => {
expect(actual).toEqual(expected);
});
});

describe('Arrangements2', () => {
let arrangements: Arrangements2;

it('should be created', () => {
const childObj: ChildInfo[] = FLOW_LIST.map((x) => ({
position: x,
elRect: { width: 200, height: 200 } as any,
}));

arrangements = new Arrangements2(childObj);
arrangements.verticalPadding = 20;
arrangements.groupPadding = 100;
const expected = {
'1': { x: 330, y: 0, id: '1', deps: [] },
'2': { x: 110, y: 300, id: '2', deps: ['1'] },
'3': { x: 0, y: 600, id: '3', deps: ['2'] },
'4': { x: 220, y: 600, id: '4', deps: ['2'] },
'5': { x: 550, y: 300, id: '5', deps: ['1'] },
'6': { x: 440, y: 600, id: '6', deps: ['5'] },
'7': { x: 660, y: 600, id: '7', deps: ['5'] },
'8': { x: 660, y: 900, id: '8', deps: ['6', '7'] },
};
const actual = Object.fromEntries(arrangements.autoArrange());
expect(actual).toEqual(expected);
});
});
117 changes: 112 additions & 5 deletions projects/flow/src/lib/arrangements.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FlowOptions, ChildInfo } from './flow-interface';
import { FlowOptions, ChildInfo, FlowDirection } from './flow-interface';

export class Arrangements {
constructor(
Expand All @@ -21,12 +21,12 @@ export class Arrangements {

for (const baseNode of baseNodes) {
if (this.direction === 'horizontal') {
this.positionDependents(baseNode, currentX, 0, newItems);
currentX += baseNode.elRect.width + this.horizontalPadding;
this.positionDependents(baseNode, 0, currentY, newItems);
currentY += baseNode.elRect.height + this.verticalPadding;
} else {
// Vertical arrangement
this.positionDependents(baseNode, 0, currentY, newItems);
currentY += baseNode.elRect.height + this.horizontalPadding;
this.positionDependents(baseNode, 0, currentX, newItems);
currentX += baseNode.elRect.width + this.verticalPadding;
}
}

Expand Down Expand Up @@ -116,3 +116,110 @@ export class Arrangements {
return { consumedSpace, dep: dependents.length > 0 };
}
}

const ROOT_DATA = new Map<string, ArrangeNode>();
const ROOT_DEPS = new Map<string, string[]>();
const HORIZONTAL_PADDING = 100;
const VERTICAL_PADDING = 20;

export class Arrangements2 {
root: string[] = [];

constructor(
private list: ChildInfo[],
private direction: FlowDirection = 'vertical',
public horizontalPadding = 100,
public verticalPadding = 20,
public groupPadding = 20
) {
ROOT_DATA.clear();
ROOT_DEPS.clear();
this.list.forEach((item) => {
ROOT_DATA.set(
item.position.id,
new ArrangeNode(item.position, item.elRect)
);
item.position.deps.forEach((dep) => {
let d = ROOT_DEPS.get(dep) || [];
d.push(item.position.id);
ROOT_DEPS.set(dep, d);
});

if (item.position.deps.length === 0) {
this.root.push(item.position.id);
}
});
}

public autoArrange(): Map<string, FlowOptions> {
this.root.forEach((id) => {
const node = ROOT_DATA.get(id)!;
node.arrange(0, 0, this.direction);
});

const newItems = new Map<string, FlowOptions>();

for (const item of this.list) {
newItems.set(item.position.id, item.position);
}
return newItems;
}
}

interface Coordinates {
x: number;
y: number;
}

export class ArrangeNode {
constructor(public position: FlowOptions, public elRect: DOMRect) {}

get deps() {
return ROOT_DEPS.get(this.position.id) || [];
}

// we need to recursively call this method to get all the dependents of the node
// and then we need to position them
arrange(x = 0, y = 0, direction: FlowDirection): Coordinates {
const dependents = ROOT_DEPS.get(this.position.id) || [];
let startX = x;
let startY = y;
let len = dependents.length;

if (len) {
if (direction === 'horizontal') {
startX += this.elRect.width + HORIZONTAL_PADDING;
} else {
startY += this.elRect.height + HORIZONTAL_PADDING;
}
let first, last: Coordinates;
for (let i = 0; i < len; i++) {
const dep = dependents[i];
const dependent = ROOT_DATA.get(dep)!;
const { x, y } = dependent.arrange(startX, startY, direction);
// capture the first and last dependent
if (i === 0) first = dependent.position;
if (i === len - 1) last = dependent.position;

if (direction === 'horizontal') {
startY = y + VERTICAL_PADDING;
} else {
startX = x + VERTICAL_PADDING;
}
}
if (direction === 'horizontal') {
startY -= VERTICAL_PADDING + this.elRect.height;
y = first!.y + (last!.y - first!.y) / 2;
} else {
startX -= VERTICAL_PADDING + this.elRect.width;
x = first!.x + (last!.x - first!.x) / 2;
}
}
this.position.x = x;
this.position.y = y;

return direction === 'horizontal'
? { x: startX, y: startY + this.elRect.height }
: { x: startX + this.elRect.width, y: startY };
}
}
5 changes: 3 additions & 2 deletions projects/flow/src/lib/flow-child.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
SimpleChanges,
ChangeDetectionStrategy,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Subject, Subscription } from 'rxjs';
import { FlowService } from './flow.service';
import { FlowOptions } from './flow-interface';
Expand Down Expand Up @@ -70,6 +69,7 @@ export class FlowChildComponent implements OnInit, OnChanges, OnDestroy {

private positionChange = new Subject<FlowOptions>();
private mouseMoveSubscription: Subscription;
private layoutSubscribe: Subscription;

constructor(
public el: ElementRef<HTMLDivElement>,
Expand All @@ -89,7 +89,7 @@ export class FlowChildComponent implements OnInit, OnChanges, OnDestroy {
});
});

this.flow.layoutUpdated.pipe(takeUntilDestroyed()).subscribe((x) => {
this.layoutSubscribe = this.flow.layoutUpdated.subscribe((x) => {
this.position = this.flow.items.get(this.position.id) as FlowOptions;
this.positionChange.next(this.position);
});
Expand Down Expand Up @@ -171,6 +171,7 @@ export class FlowChildComponent implements OnInit, OnChanges, OnDestroy {

ngOnDestroy() {
this.disableDragging();
this.layoutSubscribe.unsubscribe();
// remove the FlowOptions from the flow service
// this.flow.delete(this.position);
// console.log('ngOnDestroy', this.position.id);
Expand Down
20 changes: 20 additions & 0 deletions projects/flow/src/lib/flow-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,27 @@ export interface FlowOptions {
deps: string[];
}

export interface DotOptions extends FlowOptions {
/**
* The index of the dot
* top = 0
* right = 1
* bottom = 2
* left = 3
*/
dotIndex: number;
}

export class FlowConfig {
Arrows = true;
ArrowSize = 20;
}

export type FlowDirection = 'horizontal' | 'vertical';

export type ArrowPathFn = (
start: DotOptions,
end: DotOptions,
arrowSize: number,
strokeWidth: number
) => string;
Loading

0 comments on commit 9ec802b

Please sign in to comment.