3
$\begingroup$

Is it possible to have double edge labels displayed by a graph?

I tried

EdgeLabels -> {Placed["Index", 0.5], Placed["Name", 0.25]}

but it didn't work.

$\endgroup$
  • 2
    $\begingroup$ This is how it would work in general, and this is how it works for vertices: Graph[{Labeled[1, Placed[{"foo", "bar"}, {Below, Above}]], 2}, {1 <-> 2}] or Graph[{"a", "b"}, {"a" <-> "b"}, VertexLabels -> Placed[{"Name", "Index"}, {Above, Below}]]. I can't get this to work for edges though. Placed is not accepted. $\endgroup$ – Szabolcs Feb 19 at 16:15
  • $\begingroup$ @Szabolcs. Your code works fine for vertex labeling, but not for edge labeling. $\endgroup$ – m_goldberg Feb 20 at 4:21
  • $\begingroup$ @m_goldberg Yes, that's what I said. Edge labels need a different (nonstandard) syntax for Placed, and it does not seem to work with multiple labels. $\endgroup$ – Szabolcs Feb 20 at 8:52
3
$\begingroup$

I've put some time in on this proble, and have decided to post my results. I'm not completely happy with them, but I offer them to the community for consideration. I hope it inspires someone to come up with something better.

First, I looked at Szbolcs suggestion. His code for vertex labeling works well.

Graph[{"a", "b"}, {"a" <-> "b"}, VertexLabels -> Placed[{"Name", "Index"}, {Above, Below}]]

g1

But when his idea is applied to edge labeling, it fails

Graph[{"a", "b"}, {"a" <-> "b"}, EdgeLabels -> Placed[{"Name", "Index"}, {Above, Below}]]

g2

It isn't that Placed can't be used with edge labels, but that, in this situation, when it is given lists as arguments, it ignores them.

Graph[{"a", "b"}, {"a" \[UndirectedEdge] "b"}, 
  EdgeLabels -> Placed["Name", .75],
  ImagePadding -> {{Automatic, Automatic}, {Automatic, 5}}]

g3

I was able to work out a way of putting two items in an edge label, but it is rather cumbersome.

With[{
       verts = {"a", "b"}, edges = {"a" \[UndirectedEdge] "b"}, 
       indents = {0}, offsets = {.75}
     },
  Module[{g, e, i},
    g = Graph[verts, edges];
    e = EdgeList[g];
    i = (EdgeIndex[g, #] & /@ e);
    Graph[e,
      EdgeLabels -> 
        MapThread[
          #1 -> Placed[Style[Row@{Spacer[#3], #2, Spacer[30], #1}, 12], #4] &, 
          {e, i, indents, offsets}]]]]

g4

My approach has enough wiggle room that it is not hard to apply it to the basic undirected graph example given in the Wolfram Documentation Center; viz.

Graph[{1 \[UndirectedEdge] 2, 2 \[UndirectedEdge] 3, 3 \[UndirectedEdge] 1}]

Like so:

With[{
       edges = {1 \[UndirectedEdge] 2, 2 \[UndirectedEdge] 3, 3 \[UndirectedEdge] 1},
       indents = {30, 20, 40},offsets = {.25, .5, .5}},
   Module[{g, e, i},
     g = Graph[edges];
     e = EdgeList[g];
     i = (EdgeIndex[g, #] & /@ e);
     Graph[e,
       EdgeLabels -> 
         MapThread[
           #1 -> Placed[Style[Row@{Spacer[#3], #2, Spacer[30], #1}, 12], #4] &, 
           {e, i, indents, offsets}]]]] 

g5

But I am not sanguine about it working well with a really complex graph.

$\endgroup$
3
$\begingroup$

There is a standard way to add more than one label with Placed, but unfortunately this does not work with edge labels. With vertex labels it would work like this:

Graph[{"a", "b"}, {"a" <-> "b"}, 
 VertexLabels -> Placed[{"Name", "Index"}, {Above, Below}]]

enter image description here

But EdgeLabels uses a special position specification in Placed, and it does not seem to be compatible with multiple labels.

Instead, we can add and position the labels manually.

First, we will need a function that returns an index-based edge list. I recommend the highly optimized IGIndexEdgeList from IGraph/M, but you can also do this without packages. Thus use either

<<IGraphM`
indexEdgeList = IGIndexEdgeList

or

indexEdgeList = List @@@ EdgeList@IndexGraph[#] &

Here's a test graph:

g = CycleGraph[5]

enter image description here

Get the point pair describing each edge:

pts = GraphEmbedding[g];
ptPairs = pts[[#]] & /@ indexEdgeList[g];

Create a labelling function:

Clear[position]
position[p_][label_, {a_, b_}] := Text[label, a + (b - a) p]

Add the labels through Epilog:

Graph[
 g,
 Epilog -> {
   MapThread[position[0.25], {Range@EdgeCount[g], ptPairs}],
   MapThread[position[0.75], {EdgeList[g], ptPairs}]
   }
 ]

enter image description here

We could improve on this with more advanced labelling functions:

Clear[position]
position[p_, offset_: {0, 0}][label_, {a_, b_}] := 
 With[{pt = a + (b - a) p},
  Rotate[
    Text[
      label,
      pt,
      offset,
      Background -> White
    ],
    ArcTan @@ Subtract @@ ReverseSort[{a, b}],
    pt
  ]
 ]

Graph[
 g,
 Epilog -> {
   MapThread[position[0.25], {Range@EdgeCount[g], ptPairs}],
   MapThread[position[0.75], {EdgeList[g], ptPairs}]
   }
 ]

enter image description here

Graph[
 g,
 Epilog -> {
   MapThread[position[0.5, {0, -2}], {Range@EdgeCount[g], ptPairs}],
   MapThread[position[0.5, {0, 2}], {EdgeList[g], ptPairs}]
   },
 PlotRangePadding -> Scaled[.05]
 ]

enter image description here

You could adapt this to your own use cases. Instead of setting Background -> White in Text (which was the simplest way), I recommend adding a Framed to the labels.

For example,

Framed[label,
 FrameStyle -> None, (* usually  we just want the background, not the frame *)
 Background -> LightBlue, (* make it White to mask the objects behind the label *)
 FrameMargins -> 1, (* tune the margin to your liking *)
 ContentPadding -> True (* set to False to have a tighter margin around simple text *)
]
$\endgroup$
2
$\begingroup$

An approach using a custom EdgeShapeFunction:

The function eSF takes a Graph object (g) and a list of {label, position, offset} triples as input to label the edges of g.

The advantage of this approach is that it can handle curved edges easily.

ClearAll[eSF]
eSF[g_Graph, lbls_] := Module[{pts = #, bsF = BSplineFunction[#], e = #2}, 
  {If[DirectedGraphQ[g], Arrow[pts, .07], Line[pts]], 
 Text[Framed[# /. {"Name" -> e, "Index" -> EdgeIndex[g, e], 
        p_ :> (PropertyValue[g, p] /. $Failed -> p)}, 
     FrameStyle -> None, FrameMargins -> 0], bsF[#2], #3, bsF'[#2],
     Background -> If[Abs[#3[[2]]] == 0, White, None]] & @@@ lbls}] &;

Examples:

g1 = CycleGraph[5, VertexLabels -> Placed["Name", Center], 
   VertexSize -> Scaled[.07],  VertexLabelStyle -> 16, 
   BaseStyle -> {FontSize -> 14, FontColor -> Black}, ImageSize -> Medium];
g2 = DirectedGraph[g1, VertexLabels -> Placed["Name", Center], 
   VertexSize -> Scaled[.07],  VertexLabelStyle -> 16, 
   BaseStyle -> {FontSize -> 14, FontColor -> Black}, 
   ImageSize -> Medium, PerformanceGoal -> "Quality",
   GraphLayout -> {"EdgeLayout" -> {"DividedEdgeBundling", 
       "CoulombConstant" -> -15, "VelocityDamping" -> .2, 
       "NewForce" -> False, "Compatibility" -> False}}];

Row[{g1, g2}]

enter image description here

txts = {"Name", "Index"};
pos1 = {.25, .75}; off1 = {{0, 0}, {0, 0}};
pos2 = {.5, .5}; off2 = {{0, -1}, {0, 1}};

lbls1 = Thread[{txts, pos1, off1}]

{{"Name", 0.25, {0, 0}}, {"Index", 0.75, {0, 0}}}

lbls2 = Thread[{txts, pos2, off2}]

{{"Name", 0.5, {0, -1}}, {"Index", 0.5, {0, 1}}}

Grid[Table[SetProperty[#, EdgeShapeFunction -> eSF[#, l]] & /@ {g1, g2},
  {l, {lbls1, lbls2}}]]

enter image description here

If needed, we can also use it with different labeling settings for each edge:

txts2 = {"label1", "label2"};
lbls3 = Thread[{txts2, pos1, off1}]

{{"label1", 0.25, {0, 0}}, {"label2", 0.75, {0, 0}}}

Grid@Table[Graph[VertexList[#], EdgeList[#], 
     EdgeShapeFunction -> { _ :> eSF[#, lbl], #2 -> eSF[#, lbls3]}, 
     Options[#]] & @@@ 
   Transpose[{{g1, g2}, {2 \[UndirectedEdge] 3, 2 \[DirectedEdge] 3}}],
 {lbl, {lbls1, lbls2}}]

enter image description here

Using the form EdgeShapeFunction -> {e_:> func} we can use eSF to label edges individually:

SeedRandom[1]
randomlabels = Thread[{With[{col = RandomColor[]}, 
       Style[#, Opacity[1], FontSize -> Scaled[.04], col] & /@ 
        StringTake[#, UpTo[4]]], {.2, .5, .8}, {0, 0}}, List, 2] & /@ 
   Partition[RandomWord["Noun", 3 EdgeCount[g2]], 3];

labeling = AssociationThread[EdgeList[g2] -> randomlabels];

Graph[VertexList[g2], EdgeList[g2], 
 EdgeShapeFunction -> {e_ :> eSF[g2, labeling[e]]}, 
 EdgeStyle -> Directive[Arrowheads[0], AbsoluteThickness[3]], 
 ImageSize -> 500, Options[g2]]

enter image description here

Credit: I learned about the many parameters of "DividedEdgeBundling" from this answer by István Zachar.

$\endgroup$
  • $\begingroup$ Very nice! I love the DividedEdgeBundling style for these cycle graphs. $\endgroup$ – Szabolcs Feb 22 at 10:55
  • $\begingroup$ Thank you @Szabolcs. I had no idea know how to use it until i bumped into Istvan's post. Afaik the documentation is still a single line on this:( $\endgroup$ – kglr Feb 22 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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