C# 绘制有向无环图:最小化边交叉?

C# 绘制有向无环图:最小化边交叉?,c#,wpf,algorithm,graph,graph-drawing,C#,Wpf,Algorithm,Graph,Graph Drawing,在DAG中以树形布局垂直面(即顶部无in边的垂直面,仅依赖于下一层的垂直面等)非常简单,无需高效杉山等图形绘制算法。然而,有没有一个简单的算法来做到这一点,尽量减少边缘交叉?(对于某些图形,可能不可能完全消除边缘交叉。)一张图片可以显示一千个单词,因此有一种算法可以提供建议。() 编辑:结果 我已经接受了Senthil的建议graphviz/dot——快速查看文档可以确认它非常容易,而且。然而,我最终选择了使用,因为我已经在使用.NET等(尽管它肯定没有dot强大)。结果是“足够好”,并且可以通

在DAG中以树形布局垂直面(即顶部无in边的垂直面,仅依赖于下一层的垂直面等)非常简单,无需高效杉山等图形绘制算法。然而,有没有一个简单的算法来做到这一点,尽量减少边缘交叉?(对于某些图形,可能不可能完全消除边缘交叉。)一张图片可以显示一千个单词,因此有一种算法可以提供建议。()

编辑:结果 我已经接受了Senthil的建议graphviz/dot——快速查看文档可以确认它非常容易,而且。然而,我最终选择了使用,因为我已经在使用.NET等(尽管它肯定没有dot强大)。结果是“足够好”,并且可以通过一点边缘布线和调整(模糊文本是因为)

这是完整的C代码(这是所有引用QuickGraph或GraphSharp的代码——是的,很简单):

内部静态类布局管理器
{
私有常量字符串算法\u NAME=“EfficientSugiyama”;
private const bool MINIMIZE_EDGE_LENGTH=true;
私有常数双顶点距离=25;
专用const双层_距离=25;
专用常数双最小值偏移=20;
公共静态空间布局(GraphCanvas画布)
{
//TODO使用后台线程
//待办事项添加评论
canvas.IsEnabled=false;
canvas.Cursor=Cursors.Wait;
var-graph=新的双向图();
var positions=newdictionary();
变量大小=新字典();
foreach(canvas.nodes中的var节点)
{
变量大小=node.RenderSize;
添加顶点(节点);
添加(节点,新点(node.left+size.Width/2,node.top+size.Height/2));
添加(节点、大小);
}
foreach(画布中的var-edge.edges)
{
图.增边(新布局(边));
}
var context=新的LayoutContext(图形、位置、大小、LayoutMode.Simple);
var参数=新的EfficientSugiyamaLayoutParameters();
parameters.VertexDistance=顶点距离;
parameters.minimizedGelength=最小化边长度;
parameters.LayerDistance=层距离;
var factory=新标准布局算法工厂();
var algorithm=factory.CreateAlgorithm(算法名称、上下文、参数);
Compute()算法;
canvas.all();
var minx=algorithm.VertexPositions.Select(kvp=>kvp.Value.X-(kvp.Key.RenderSize.Width/2)).Aggregate(Math.Min);
var miny=algorithm.VertexPositions.Select(kvp=>kvp.Value.Y-(kvp.Key.RenderSize.Height/2)).Aggregate(Math.Min);
minx-=最小画布偏移量;
miny-=最小画布偏移量;
minx=minx<0?-minx:0;
miny=miny<0?-miny:0;
foreach(算法中的var kvp.VertexPositions)
{
var node=kvp.Key;
var pos=千伏值;
node.left=(pos.X-(node.renderize.Width/2))+minx;
node.top=(pos.Y-(node.renderize.Height/2))+miny;
}
canvas.Cursor=Cursors.Arrow;
canvas.IsEnabled=true;
}
私人密封类布局:IEdge
{
专用只读连接边缘;
公共布局(连接边缘){u边缘=边缘;}
公共GraphNode源{get{return}edge.output.node;}
公共GraphNode目标{get{return}edge.input.node;}
}
您可以尝试使用。在第一步中,您可以通过执行拓扑排序并始终在单个层中对独立节点分组来确定布局的级别(从上到下)。对于有向无环图,这将始终成功


然后,您可以尝试对每一层(从左到右)进行拓扑排序,同时考虑输入和输出端口的位置以及可能的相邻层。我对这一步骤的描述有点模糊,但我可以想象,这对于像您的示例这样的图来说是可行的。

Dot似乎符合要求:

点-`hierarchical''或分层 有向图的绘制 布局算法的目标是图像中的边缘 相同方向(从上到下或从左 然后试图避免 边缘交叉和减少边缘长度


我认为杉山方法应该最小化边缘交叉?也许你提到的“高效”版本忽略了这一步。@Cory Larson-是的;我可以使用这样的布局算法,但我想知道是否有任何方法可以利用图的非循环特性(并且总是将顶点放在依赖它们的顶点之上)太棒了;看起来很完美——嗯……如何将graphviz与WPF控件定位集成(而不是渲染位图然后扫描像素)@Robert,看看graphviz.org,dot算法有很多种实现方式,也许有一种实现方式能很好地适应dotNET环境。或者,你可以运行
dot.exe
程序,让它输出一组坐标,你可以用它在
WPF
@Elazar中放置控件——太棒了,谢谢!看起来不错例如,“普通ext”输出格式很容易解析,.dot文件很容易生成(但我最终选择了另一种解决方案;请参阅更新的答案)。我知道我已经晚了将近三年,但dot在避免边缘交叉方面不太好。在中大型图形中,特别是如果您使用群集,很容易得到带有许多线交叉的可怕图形,这些图形很容易被手动消除。此外,dot布局非常不稳定:如果您添加或删除边缘或节点,或even若只更改边标签或节点标签,dot可能会以完全不同的方式呈现图形,使所有的t变得无用
internal static class LayoutManager
{
    private const string ALGORITHM_NAME = "EfficientSugiyama";
    private const bool MINIMIZE_EDGE_LENGTH = true;
    private const double VERTEX_DISTANCE = 25;
    private const double LAYER_DISTANCE = 25;
    private const double MIN_CANVAS_OFFSET = 20;

    public static void doLayout(GraphCanvas canvas)
    {
        // TODO use a background thread
        // TODO add comments
        canvas.IsEnabled = false;
        canvas.Cursor = Cursors.Wait;
        var graph = new BidirectionalGraph<GraphNode, LayoutEdge>();
        var positions = new Dictionary<GraphNode, Point>();
        var sizes = new Dictionary<GraphNode, Size>();
        foreach(var node in canvas.nodes)
        {
            var size = node.RenderSize;
            graph.AddVertex(node);
            positions.Add(node, new Point(node.left + size.Width / 2, node.top + size.Height / 2));
            sizes.Add(node, size);
        }
        foreach(var edge in canvas.edges)
        {
            graph.AddEdge(new LayoutEdge(edge));
        }

        var context = new LayoutContext<GraphNode, LayoutEdge, BidirectionalGraph<GraphNode, LayoutEdge>>(graph, positions, sizes, LayoutMode.Simple);
        var parameters = new EfficientSugiyamaLayoutParameters();
        parameters.VertexDistance = VERTEX_DISTANCE;
        parameters.MinimizeEdgeLength = MINIMIZE_EDGE_LENGTH;
        parameters.LayerDistance = LAYER_DISTANCE;
        var factory = new StandardLayoutAlgorithmFactory<GraphNode, LayoutEdge, BidirectionalGraph<GraphNode, LayoutEdge>>();
        var algorithm = factory.CreateAlgorithm(ALGORITHM_NAME, context, parameters);
        algorithm.Compute();
        canvas.deselectAll();

        var minx = algorithm.VertexPositions.Select(kvp => kvp.Value.X - (kvp.Key.RenderSize.Width / 2)).Aggregate(Math.Min);
        var miny = algorithm.VertexPositions.Select(kvp => kvp.Value.Y - (kvp.Key.RenderSize.Height / 2)).Aggregate(Math.Min);
        minx -= MIN_CANVAS_OFFSET;
        miny -= MIN_CANVAS_OFFSET;
        minx = minx < 0 ? -minx : 0;
        miny = miny < 0 ? -miny : 0;
        foreach(var kvp in algorithm.VertexPositions)
        {
            var node = kvp.Key;
            var pos = kvp.Value;
            node.left = (pos.X - (node.RenderSize.Width / 2)) + minx;
            node.top = (pos.Y - (node.RenderSize.Height / 2)) + miny;
        }
        canvas.Cursor = Cursors.Arrow;
        canvas.IsEnabled = true;
    }

    private sealed class LayoutEdge : IEdge<GraphNode>
    {
        private readonly ConnectingEdge _edge;
        public LayoutEdge(ConnectingEdge edge) { _edge = edge; }
        public GraphNode Source { get { return _edge.output.node; } }
        public GraphNode Target { get { return _edge.input.node; } }
    }