Skip to content

Commit

Permalink
Allow user to modify the default snap point for snapped edges.
Browse files Browse the repository at this point in the history
It can be done by setting anchor and offset, as per https://docs.google.com/document/d/1qlJe14hcgZSFz8wIJVAFbKTxEPYlG1IP-k4VSIbFur0/edit?resourcekey=0-6HRr6318X_-zM8HgjPDrZQ&tab=t.0#heading=h.c0uts5ftkk58

Z-index issue has not been solved, as it is more complex than anticipated:
- edge should render over both nodes it is connected to
- edge should render below any potential third node it might intersect with
- it is easily possible to show example where there is no correct ordering

Solution to this exists where edge would be rendered 3 times:
- first once as currently
- then twice over both end nodes, within the scope of bounding box of that node and with overflow disabled

But that would further decrease the performance of the component + this is not a trivial change

PiperOrigin-RevId: 712891596
  • Loading branch information
Googler authored and copybara-github committed Jan 8, 2025
1 parent 49144e5 commit 9a80115
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 31 deletions.
24 changes: 22 additions & 2 deletions src/app/directed_acyclic_graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import {ScreenshotTest} from '../screenshot_test';
import {ColorThemeLoader} from './color_theme_loader';
import {DagStateService} from './dag-state.service';
import {STATE_SERVICE_PROVIDER} from './dag-state.service.provider';
import {DirectedAcyclicGraph, DirectedAcyclicGraphModule} from './directed_acyclic_graph';
import {DirectedAcyclicGraph, DirectedAcyclicGraphModule, generateTheme} from './directed_acyclic_graph';
import {DagNode as Node, type GraphSpec, type NodeRef} from './node_spec';
import {TEST_IMPORTS, TEST_PROVIDERS} from './test_providers';
import {DirectedAcyclicGraphHarness} from './test_resources/directed_acyclic_graph_harness';
import {createDagSkeletonWithCustomGroups, createDagSkeletonWithGroups, fakeGraph} from './test_resources/fake_data';
import {createDagSkeletonWithCustomGroups, createDagSkeletonWithGroups, fakeGraph, fakeGraphWithEdgeOffsets} from './test_resources/fake_data';
import {initTestBed} from './test_resources/test_utils';

const FAKE_DATA: GraphSpec =
Expand Down Expand Up @@ -212,6 +212,24 @@ describe('Directed Acyclic Graph Renderer', () => {
await screenShot.expectMatch(`graph_loading`);
});
});

describe('with edge offsets', () => {
let fixture: ComponentFixture<TestComponent>;

beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
const graphSpec = Node.createFromSkeleton(
fakeGraphWithEdgeOffsets.skeleton, fakeGraphWithEdgeOffsets.state);
fixture.componentRef.setInput('graph', graphSpec);
fixture.componentRef.setInput(
'theme', generateTheme({edgeStyle: 'snapped'}));
fixture.detectChanges();
});

it('renders correctly', async () => {
await screenShot.expectMatch(`graph_with_edge_offsets`);
});
});
});
});

Expand All @@ -226,6 +244,7 @@ describe('Directed Acyclic Graph Renderer', () => {
[followNode]="followNode"
[loading]="loading"
[customNodeTemplates]="{'outlineBasic': outlineBasic}"
[theme]="theme"
>
</ai-dag-renderer>
</div>
Expand Down Expand Up @@ -267,4 +286,5 @@ class TestComponent {
@Input() graph: GraphSpec = FAKE_DATA;
@Input() followNode: NodeRef|null = null;
@Input() loading = false;
@Input() theme = generateTheme({});
}
84 changes: 56 additions & 28 deletions src/app/directed_acyclic_graph_raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {fetchIcon, generateFullIconFor} from './icon_util';
import {WorkflowGraphIconModule} from './icon_wrapper';
import {DagNodeModule} from './node';
import {NodeRefBadgeModule} from './node_ref_badge';
import {CustomNode, type DagEdge, DagGroup, DagNode, GroupIterationRecord, GroupToggleEvent, isDagreInit, isSamePath, NodeMap, NodeRef, NodeType, Point, type SelectedNode} from './node_spec';
import {CustomNode, type DagEdge, DagGroup, DagNode, GroupIterationRecord, GroupToggleEvent, isDagreInit, isSamePath, NodeMap, NodeRef, NodeType, Point, type SelectedNode, SnapPoint} from './node_spec';
import {UserConfigService} from './user_config.service';
import {debounce, isPinch} from './util_functions';

Expand Down Expand Up @@ -735,17 +735,8 @@ export class DagRaw implements DoCheck, OnInit, OnDestroy {
this.cdr.detectChanges();
}

getTopCenterPoint(node: DagNode|DagGroup): Point {
return {x: node.x, y: node.y - node.height / 2};
}
getBottomCenterPoint(node: DagNode|DagGroup): Point {
return {x: node.x, y: node.y + node.height / 2};
}
getLeftCenterPoint(node: DagNode|DagGroup): Point {
return {x: node.x - node.width / 2, y: node.y};
}
getRightCenterPoint(node: DagNode|DagGroup): Point {
return {x: node.x + node.width / 2, y: node.y};
anchorPoint(length: number, percent: number, offset: number) {
return length * (percent - 0.5) + offset;
}

/**
Expand All @@ -768,27 +759,64 @@ export class DagRaw implements DoCheck, OnInit, OnDestroy {
const toTarget = this.ensureTargetEntity(edge.to);

const layoutDirection = this.layout.rankDirection;

let startHorizontalAnchor = edge.startSnapPoint?.horizontalPercent;
let startVerticalAnchor = edge.startSnapPoint?.verticalPercent;
let endHorizontalAnchor = edge.endSnapPoint?.horizontalPercent;
let endVerticalAnchor = edge.endSnapPoint?.verticalPercent;

if (layoutDirection === 'LR') {
edge.points = [
this.getRightCenterPoint(fromTarget),
this.getLeftCenterPoint(toTarget),
];
startHorizontalAnchor ??= 1;
startVerticalAnchor ??= 0.5;
endHorizontalAnchor ??= 0;
endVerticalAnchor ??= 0.5;
} else if (layoutDirection === 'RL') {
edge.points = [
this.getLeftCenterPoint(fromTarget),
this.getRightCenterPoint(toTarget),
];
startHorizontalAnchor ??= 0;
startVerticalAnchor ??= 0.5;
endHorizontalAnchor ??= 1;
endVerticalAnchor ??= 0.5;
} else if (layoutDirection === 'BT') {
edge.points = [
this.getTopCenterPoint(fromTarget),
this.getBottomCenterPoint(toTarget),
];
startHorizontalAnchor ??= 0.5;
startVerticalAnchor ??= 0;
endHorizontalAnchor ??= 0.5;
endVerticalAnchor ??= 1;
} else {
edge.points = [
this.getBottomCenterPoint(fromTarget),
this.getTopCenterPoint(toTarget),
];
startHorizontalAnchor ??= 0.5;
startVerticalAnchor ??= 1;
endHorizontalAnchor ??= 0.5;
endVerticalAnchor ??= 0;
}

const startPoint = {
x: fromTarget.x +
this.anchorPoint(
fromTarget.width,
startHorizontalAnchor,
edge.startSnapPoint?.horizontalOffset ?? 0,
),
y: fromTarget.y +
this.anchorPoint(
fromTarget.height,
startVerticalAnchor,
edge.startSnapPoint?.verticalOffset ?? 0,
),
};
const endPoint = {
x: toTarget.x +
this.anchorPoint(
toTarget.width,
endHorizontalAnchor,
edge.endSnapPoint?.horizontalOffset ?? 0,
),
y: toTarget.y +
this.anchorPoint(
toTarget.height,
endVerticalAnchor,
edge.endSnapPoint?.verticalOffset ?? 0,
),
};

edge.points = [startPoint, endPoint];
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/app/node_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ export class DagGroup implements
}
}

// Snap point of the edge
export interface SnapPoint {
// Defaults to how snap points are now calculated
horizontalPercent?: number;
verticalPercent?: number;
// Defaults to 0, 0
horizontalOffset?: number;
verticalOffset?: number;
}

/**
* Dag Edge relationship
*
Expand All @@ -436,6 +446,9 @@ export interface DagEdge {
weight?: number;
/** The number of ranks to keep between the source and target of the edge. */
minlen?: number;
// Only relevant for snapped edges
startSnapPoint?: SnapPoint;
endSnapPoint?: SnapPoint;
}

/**
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 67 additions & 1 deletion src/app/test_resources/fake_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* various testing scenarios
*/

import {baseColors, CustomNode, DagNode, DagNodeSkeleton, DagSkeleton, repeatedMetaNodes, StateTable} from '../node_spec';
import {baseColors, CustomNode, DagNode, DagNodeSkeleton, DagSkeleton, repeatedMetaNodes, SnapPoint, StateTable} from '../node_spec';

/**
* The standard Fake Graph that can be used by `DagNode.createFromSkeleton()` to
Expand Down Expand Up @@ -657,3 +657,69 @@ export function createDagSkeletonWithGroups(treatAsLoop: boolean): DagSkeleton {
] as DagNodeSkeleton[],
};
}

export const fakeGraphWithEdgeOffsets: DagSkeleton = {
skeleton: [
{
id: 'node1',
type: 'execution',
next: [
{
id: 'node2',
type: 'execution',
edgeOpts: {
startSnapPoint: {
horizontalOffset: 5,
verticalOffset: 15,
horizontalPercent: 0.4,
verticalPercent: 0.6,
},
endSnapPoint: {
horizontalPercent: 0.5,
verticalPercent: 0.5,
},
},
},
],
},
{
id: 'node2',
type: 'execution',
next: [
{
id: 'node3',
type: 'execution',
edgeOpts: {
startSnapPoint: {
horizontalOffset: 15,
verticalOffset: -5,
},
endSnapPoint: {
horizontalOffset: 30,
verticalPercent: 0.2,
},
},
},
{
id: 'node4',
type: 'execution',
edgeOpts: {
startSnapPoint: {},
endSnapPoint: {
horizontalAnchor: -0.1,
verticalAnchor: -0.1,
},
},
},
],
},
{
id: 'node3',
type: 'execution',
},
{
id: 'node4',
type: 'execution',
},
] as DagNodeSkeleton[],
};

0 comments on commit 9a80115

Please sign in to comment.