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.
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. – Szabolcs Feb 19 '19 at 16:15Placed, and it does not seem to work with multiple labels. – Szabolcs Feb 20 '19 at 08:52