8
$\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$
3
  • 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$ Commented Feb 19, 2019 at 16:15
  • $\begingroup$ @Szabolcs. Your code works fine for vertex labeling, but not for edge labeling. $\endgroup$ Commented Feb 20, 2019 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$ Commented Feb 20, 2019 at 8:52

3 Answers 3

10
$\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$
2
  • $\begingroup$ Very nice! I love the DividedEdgeBundling style for these cycle graphs. $\endgroup$ Commented Feb 22, 2019 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$ Commented Feb 22, 2019 at 11:05
6
$\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$
5
$\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$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.