| Name | Message | Date |
|---|---|---|
| 📄 GraphLayout.cs | 5 days ago | |
| 📄 GraphScene.cs | 5 days ago |
📄
ui/src/Graph/GraphScene.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
using System; using System.Collections.Generic; using Avalonia; using Microsoft.CodeAnalysis; namespace Cfa.Ui.Graph; public sealed record GraphScene(IReadOnlyList<GraphNode> Nodes, IReadOnlyList<GraphEdge> Edges) { /// <summary>Conservative viewport bounds including connection curves, strokes, and arrowheads.</summary> public Rect Bounds { get; } = CalculateBounds(Nodes, Edges); private static Rect CalculateBounds(IReadOnlyList<GraphNode> nodes, IReadOnlyList<GraphEdge> edges) { Rect? bounds = null; foreach (var node in nodes) bounds = bounds?.Union(node.Bounds) ?? node.Bounds; foreach (var edge in edges) { // A cubic curve lies within its control-point hull; 15 also covers the 13-by-6 arrowhead. var curve = Envelope(edge.Start, edge.End) .Union(Envelope(edge.Control1, edge.Control2)) .Inflate(15 + edge.Thickness / 2); bounds = bounds?.Union(curve) ?? curve; } return bounds ?? default; static Rect Envelope(Point first, Point second) => new( Math.Min(first.X, second.X), Math.Min(first.Y, second.Y), Math.Abs(first.X - second.X), Math.Abs(first.Y - second.Y) ); } } public sealed class GraphNode { internal GraphNode( int id, ISymbol symbol, string label, string? sourceLink, Size size, bool isOrigin, bool isCycleReference ) { Id = id; Symbol = symbol; Label = label; SourceLink = sourceLink; Width = size.Width; Height = size.Height; IsOrigin = isOrigin; IsCycleReference = isCycleReference; } public int Id { get; } public ISymbol Symbol { get; } public string Label { get; } public string? SourceLink { get; } public Point Location { get; internal set; } public double Width { get; } public double Height { get; } public bool IsOrigin { get; } public bool IsCycleReference { get; } public Rect Bounds => new(Location, new Size(Width, Height)); } public sealed record GraphEdge( GraphNode Source, GraphNode Target, Point Start, Point End, double Thickness, bool IsLoopback ) { /// <summary>Cubic control points shared by connection rendering and viewport fitting.</summary> public Point Control1 => IsLoopback ? new Point(Start.X + 100, Start.Y - 100) : new Point(Start.X + Math.Max(40, Math.Abs(End.X - Start.X) * 0.45), Start.Y); public Point Control2 => IsLoopback ? new Point(End.X - 100, End.Y - 100) : new Point(End.X - Math.Max(40, Math.Abs(End.X - Start.X) * 0.45), End.Y); }