3
$\begingroup$

The "LayeredDigraphEmbedding" GraphLayout uses an edge routing algorithm which produces nice and readable diagrams and curved edges.

g = Graph[{1 -> 2, 2 -> 3, 1 -> 4, 4 -> 3, 4 -> 2, 4 -> 5}, 
  EdgeStyle -> Arrowheads[{{Medium, 0.5}}], VertexLabels -> "Name", 
  GraphLayout -> "LayeredDigraphEmbedding"]

Mathematica graphics

How can I access this edge routing algorithm without also letting it position vertices? It seems that as soon as I fix vertex coordinates, it ceases working:

coord = GraphEmbedding[g];

SetProperty[g, VertexCoordinates -> coord]

Mathematica graphics

Why do I need this? First I would like to plot an acyclic directed graph like the one above, then I would like to add additional edges (some of which introduce cycles) without disturbing either the layout of the vertices or the layout of the already existing edges. For example, I might want to add an additional edge 1 -> 3 to the above graph (without overlapping with 1 -> 2 -> 3), then yet another one from 5 -> 1 which makes it acyclic. All this time I want to keep the exiting vertex and edge locations untouched.

$\endgroup$

1 Answer 1

2
$\begingroup$

Update: A more convenient way than the original post is to generate a seperate graph with only the new edges and combine the GraphicsGroupBoxes of the two graphs:

ClearAll[graphAddF]
graphAddF = RawBoxes[With[{gg2 = Cases[ToBoxes[#2], 
      GraphicsGroupBox[x_] :> x[[1]], {0, Infinity}][[1]]}, 
     Replace[ToBoxes[#], GraphicsGroupBox[{x_, y_}] :> 
       GraphicsGroupBox[{{x, gg2}, y}], {0, Infinity}]]] &;

Examples:

g = Graph[{1 -> 2, 2 -> 3, 1 -> 4, 4 -> 3, 4 -> 2, 4 -> 5}, 
   EdgeStyle -> Arrowheads[{{Medium, 0.5}}], BaseStyle -> Thick,
   VertexLabels -> "Name", GraphLayout -> "LayeredDigraphEmbedding", 
   ImagePadding -> 10, ImageSize -> 300];

{newedgesa, newedgesb} = {{1 -> 3, 5 -> 1}, {1 -> 3, 5 -> 1, 3 -> 5}};
{curvaturesa, curvaturesb} = {{-0.5, 0}, {-0.5, 0, 0}};

g2 = Graph[newedgesa,  VertexCoordinates -> GraphEmbedding[g][[{1, 3, 5}]], 
   ImageSize -> 200, BaseStyle -> {Thick, Red}, 
   EdgeShapeFunction -> Thread[newedgesa -> (curvedArcF[{{Large, .75}}] /@ curvaturesa)]];
g3 = Graph[newedgesb,  VertexCoordinates -> GraphEmbedding[g][[{1, 3, 5}]], 
  ImageSize -> 200, BaseStyle -> {Thick, Red}, 
  EdgeShapeFunction -> Thread[newedgesb -> (curvedArcF[{{Large, .75}}] /@ curvaturesb)]]; 

Row[{g2, graphAddF[g, g2], g3, graphAddF[g, g3]}]

enter image description here


Original post:

A work-around until someone posts a more direct answer on the inner workings of edge routing in various embeddings:

to keep the existing vertex and edge locations untouched

We can construct EdgeShapeFunctions extracting the edge primitives from the box expression of a graph:

ClearAll[edgeRoutesF, curvedArcF]
edgeRoutesF[g_Graph] := Module[{grgrp = Cases[ToBoxes[g], 
   GraphicsGroupBox[x_] :> (x[[1]] /. {dirs___, sb : StyleBox[_, __] ..} :> 
    {StyleBox[#, ## & @@Flatten[{dirs, #2}]] & @@@ {sb}} /. 
    {DynamicLocation[v1_, ___], mid___, DynamicLocation[v2_, ___]} :> {v1, mid, v2}), 
  {0, Infinity}][[1, 1]], edges}, 
  edges = Cases[grgrp, {v1_String, mid___, v2_String} :> 
    (DirectedEdge @@ (ToExpression /@ StringSplit[{v1, v2}, "$"][[All, -1]])), 
    {0, Infinity}]; 
  Thread[edges -> (Function /@ (grgrp /. {v1_String, mid___, v2_String} :> 
    {#[[1]], mid, #[[2]]}))]]

To construct BezierCurves for the newly added edges we can use the built-in (but undocumented) EdgeShapeFunction "CurvedArc":

curvedArcF[ah_: {{Medium, .5}}][curv_: .5] := Composition[Style[#, Arrowheads[ah]] &, 
  Arrow, GraphElementData[{"CurvedArc", "Curvature" -> curv}]]

Examples:

g = Graph[{1 -> 2, 2 -> 3, 1 -> 4, 4 -> 3, 4 -> 2, 4 -> 5}, 
   EdgeStyle -> Arrowheads[{{Medium, 0.5}}], VertexLabels -> "Name", 
   GraphLayout -> "LayeredDigraphEmbedding", ImagePadding -> 10, ImageSize -> 300];

newedgesa = DirectedEdge @@@ {{1, 3}, {5, 1}};
curvaturesa = {-.5, -1.5};
edgeshapesa = Join[edgeRoutesF@g, 
   Thread[newedgesa -> (curvedArcF[{{Large, .75}}] /@ curvaturesa)]];

ga = SetProperty[EdgeAdd[g, newedgesa], 
   {EdgeShapeFunction -> edgeshapesa, VertexCoordinates -> GraphEmbedding[g],
    ImageSize -> 400}];

newedgesb = Join[newedgesa, {DirectedEdge[3, 5]}];
curvaturesb = Join[curvaturesa, {0}];
edgeshapesb = Join[edgeRoutesF@g, 
   Thread[newedgesb -> (curvedArcF[{{Large, .75}}] /@ curvaturesb)]];

gb = SetProperty[EdgeAdd[g, newedgesb], 
  {EdgeShapeFunction -> edgeshapesb, VertexCoordinates -> GraphEmbedding[g], 
  ImageSize -> 400}];

Row[{g, ga, gb}]

enter image description here

With GraphLayout -> "SpringElectricalEmbedding" and curvaturesa = {1.5, .5} we get

Row[{g, ga, gb}]

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.