Skip to content

Commit 532cd9f

Browse files
Googlercopybara-github
authored andcommitted
Feature Addition
---- Implement External Nodes for Groups This commit introduces the concept of external nodes to Groups. It allows a node in the parent graph to be connected to a node in a group without having to include the external node in the group itself. The main changes are: - External Target: The External target provides a `Map<string, { nodes: string[], point: Point, type: 'in'|'out' }>` for each Group - `internalEdges` input in the Graph: This array is used to provide the edges that are connecting the Group with external nodes - Rendering logic: The external target node is rendered with a default proxy node template. - Rendering logic: The edges between the external nodes and internal nodes are rendered. This feature enable the following: - Connect a Node in the main DAG to a Node in a Group without having to declare the node in the Group - Connect a Group with external Nodes - Keep a consistent look for the group. - Create complex diagrams using subgraphs. Limitations - If two nodes are in different groups or have a parental difference of more than 1 then we can't show edges between them yet. - This feature currently works in layout direction left to right or right to left, while using layout top to bottom or bottom to top there is some shifting in x axis of the node PiperOrigin-RevId: 777884537
1 parent b16adce commit 532cd9f

File tree

7 files changed

+328
-5
lines changed

7 files changed

+328
-5
lines changed

src/app/directed_acyclic_graph.ng.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
[resolveReference]="resolveReference"
7373
(edgeLabelClick)="edgeLabelClick.emit($event)"
7474
(hoveredEdgeChange)="hoveredEdgeChange.emit($event)"
75+
[internalEdges]="internalEdges"
7576
/>
7677
</section>
7778
</figure>

src/app/directed_acyclic_graph.spec.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ import {ScreenshotTest} from '../screenshot_test';
2525
import {ColorThemeLoader} from './color_theme_loader';
2626
import {DagStateService} from './dag-state.service';
2727
import {STATE_SERVICE_PROVIDER} from './dag-state.service.provider';
28+
import {type LayoutOptions, RankDirection} from './data_types_internal';
2829
import {DirectedAcyclicGraph, DirectedAcyclicGraphModule, generateTheme} from './directed_acyclic_graph';
29-
import {DagNode as Node, type GraphSpec, type NodeRef} from './node_spec';
30+
import {DagEdge, DagNode as Node, type GraphSpec, type NodeRef} from './node_spec';
3031
import {DirectedAcyclicGraphHarness} from './test_resources/directed_acyclic_graph_harness';
31-
import {createDagSkeletonWithCustomGroups, createDagSkeletonWithGroups, fakeGraph, fakeGraphWithColoredLabels, fakeGraphWithEdgeOffsets, fakeGraphWithLabelIcons, fakeGraphWithRotatedLabels} from './test_resources/fake_data';
32+
import {createDagSkeletonWithCustomGroups, createDagSkeletonWithGroups, createDagSkeletonWithNormalGroups, fakeGraph, fakeGraphWithColoredLabels, fakeGraphWithEdgeOffsets, fakeGraphWithLabelIcons, fakeGraphWithRotatedLabels} from './test_resources/fake_data';
3233
import {initTestBed} from './test_resources/test_utils';
3334

3435
const FAKE_DATA: GraphSpec =
@@ -167,6 +168,46 @@ describe('Directed Acyclic Graph Renderer', () => {
167168
});
168169
});
169170

171+
describe('with internal edges', () => {
172+
let fixture: ComponentFixture<TestComponent>;
173+
174+
afterEach(fakeAsync(() => {
175+
fixture.destroy();
176+
}));
177+
178+
async function setup(options: {
179+
hideControlNodeOnExpand?: boolean,
180+
expanded?: boolean,
181+
internalEdges?: DagEdge[]
182+
} = {}) {
183+
const {
184+
hideControlNodeOnExpand = false,
185+
expanded = false,
186+
internalEdges = [],
187+
} = options;
188+
fixture = TestBed.createComponent(TestComponent);
189+
const skeleton = createDagSkeletonWithNormalGroups(expanded);
190+
const graphSpec =
191+
Node.createFromSkeleton(skeleton.skeleton, skeleton.state);
192+
fixture.componentRef.setInput('internalEdges', internalEdges);
193+
fixture.componentRef.setInput(
194+
'rankDirection', RankDirection.LEFT_TO_RIGHT);
195+
fixture.componentRef.setInput('graph', graphSpec);
196+
fixture.detectChanges();
197+
await fixture.whenStable();
198+
}
199+
200+
it('renders correctly with group expanded and internal edges visible',
201+
async () => {
202+
await setup({
203+
expanded: true,
204+
internalEdges: [{from: 'client', to: 'node1'}]
205+
});
206+
fixture.detectChanges();
207+
await fixture.whenStable();
208+
await screenShot.expectMatch(`graph_expanded_with_internal_edges`);
209+
});
210+
});
170211
describe('with group labels', () => {
171212
let fixture: ComponentFixture<TestComponent>;
172213

@@ -298,6 +339,8 @@ describe('Directed Acyclic Graph Renderer', () => {
298339
[loading]="loading"
299340
[customNodeTemplates]="{'outlineBasic': outlineBasic}"
300341
[theme]="theme"
342+
[internalEdges]="internalEdges"
343+
[layout]="layout"
301344
>
302345
</ai-dag-renderer>
303346
</div>
@@ -337,7 +380,14 @@ describe('Directed Acyclic Graph Renderer', () => {
337380
class TestComponent {
338381
@ViewChild('dagRender', {static: false}) dagRender!: DirectedAcyclicGraph;
339382
@Input() graph: GraphSpec = FAKE_DATA;
383+
@Input() internalEdges: DagEdge[] = [];
384+
@Input() rankDirection: RankDirection = RankDirection.TOP_TO_BOTTOM;
340385
@Input() followNode: NodeRef|null = null;
341386
@Input() loading = false;
342387
@Input() theme = generateTheme({});
388+
get layout(): LayoutOptions {
389+
return {
390+
rankDirection: this.rankDirection
391+
}
392+
}
343393
}

src/app/directed_acyclic_graph.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ export class DirectedAcyclicGraph implements OnInit, OnDestroy {
202202
*/
203203
@Input() optimizeForOrm = false;
204204

205+
@Input() internalEdges: DagEdge[] = [];
206+
205207
@Input('sizeConfig')
206208
set sizeConfig(config) {
207209
this.$sizeConfig = config;

src/app/directed_acyclic_graph_raw.ng.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
*ngIf="visible"
1313
>
1414
<svg class="edge canvas" aria-hidden="true">
15+
<ng-container *ngFor="let item of externalTargets | keyvalue">
16+
<ng-container *ngFor="let node of nodes">
17+
<path
18+
*ngIf="node.id.startsWith('__proxy_in_')&& node.id.endsWith(item.key)"
19+
class="line proxy-line"
20+
[attr.d]="buildProxyPath(item.value.point,{x:node.x, y:node.y})"
21+
stroke="grey"
22+
stroke-width="2"
23+
fill="none"
24+
style="stroke-dasharray: 5, 5;"
25+
/>
26+
</ng-container>
27+
</ng-container>
1528
<ng-container *ngFor="let e of edges; trackBy: edgeTrack">
1629
<g
1730
*ngIf="isDagreInit(e)"
@@ -366,6 +379,8 @@
366379
(graphResize)="storeSubDagDims($event, group)"
367380
[resolveReference]="resolveReference"
368381
[visible]="isGroupExpanded(group)"
382+
[externalTargets]="getExternalTargetsForGroup(group)"
383+
[internalEdges]="internalEdges"
369384
></ai-dag-raw>
370385
<ng-container *ngIf="!!(group.treatAsLoop && group._cachedSelection)">
371386
<ai-dag-raw
@@ -430,3 +445,18 @@
430445
</div>
431446
</ng-container>
432447
</ng-template>
448+
449+
<ng-template #defaultProxyNodeTemplate let-node="node">
450+
<div
451+
[style.width.px]="node.width"
452+
[style.height.px]="node.height"
453+
[style.background]="'#666'"
454+
[style.border-radius]="'50%'"
455+
[style.display]="'flex'"
456+
[style.align-items]="'center'"
457+
[style.justify-content]="'center'"
458+
title="Connection Point"
459+
>
460+
<!-- No text content -->
461+
</div>
462+
</ng-template>

0 commit comments

Comments
 (0)