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.
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}] 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}}]] 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}}] 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]] Credit: I learned about the many parameters of "DividedEdgeBundling" from this answer by István Zachar.
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}]] 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] 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}] } ] 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}] } ] 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] ] 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 *) ] 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}]] But when his idea is applied to edge labeling, it fails
Graph[{"a", "b"}, {"a" <-> "b"}, EdgeLabels -> Placed[{"Name", "Index"}, {Above, Below}]] 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}}] 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}]]]] 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}]]]] But I am not sanguine about it working well with a really complex graph.
Graph[{Labeled[1, Placed[{"foo", "bar"}, {Below, Above}]], 2}, {1 <-> 2}]orGraph[{"a", "b"}, {"a" <-> "b"}, VertexLabels -> Placed[{"Name", "Index"}, {Above, Below}]]. I can't get this to work for edges though.Placedis not accepted. $\endgroup$Placed, and it does not seem to work with multiple labels. $\endgroup$