23
$\begingroup$

I'm trying to create a graph to represent transitions between different states of a system. I would like to represent the time the systems spent in a state with the size of the vertex and the frequency of transition between two states with the thickness of the edge connecting the two vertices. In short I need to control the size of vertices and the thickness of edges.

Now that I grasp the difference between Graph and GraphPlot a bit better, I wanted to implement this using the newer Graph functunality, specifically VertexSize and EdgeStyle.

Here is my clumsy attempt:

trans = Uncompress[FromCharacterCode[
    Flatten[ImageData[Import["https://i.stack.imgur.com/YX8NN.png"],"Byte"]]]];
l = VertexList[Graph[trans]];
(*Weigths for vertices*)
r = RandomReal[{0.5, 1}, Length[l]];
vs = Transpose@{l, r} /. List[a_, b_] -> Rule[a, b];
(*Adjust font size*)
vls = Transpose@{l, r} /. 
   List[a_, b_] -> Rule[a, Directive[Black, Bold, 20*b]];
(*Weigths for line thicknes*)
rr = RandomReal[{0.1, 1}, Length[DeleteDuplicates@trans]]/100;
es = (First@# -> Directive[Thickness[Last@#], Opacity[.5]]) & /@ 
   Transpose@{DeleteDuplicates@trans, rr};
g = Graph[DeleteDuplicates@trans, 
  VertexLabels -> Placed["Name", Center], 
  VertexShapeFunction -> "Capsule", VertexSize -> vs, 
  VertexLabelStyle -> vls, EdgeStyle -> es, 
  EdgeShapeFunction -> 
   GraphElementData["ShortUnfilledArrow", "ArrowSize" -> 0.03]]

But the result is useless (the text is not readable and it's not clear from where to where the arrows are going):

Resulting graph

How can I either fix this graph so that it's readable or implement variable vertex sizes and variable edge thickens using GraphLayout?

EDIT @kguler's comment helps. so using:

esf = Function[
    a, (First@
       a -> ({Arrowheads[{{.03, 1}}], 
         Arrow[#1, {.10 + 6 Last@a, .15}]} &))] /@ 
   Transpose@{DeleteDuplicates@trans, rr};
g = Graph[DeleteDuplicates@trans, 
  VertexLabels -> Placed["Name", Center], VertexSize -> vs, 
  VertexLabelStyle -> vls, EdgeStyle -> es, EdgeShapeFunction -> esf(*,
  EdgeShapeFunction->GraphElementData["ShortUnfilledArrow",
  "ArrowSize"->0.03]*)]

I get Mathematica graphics, which is almost usable. Is there perhaps a way to draw vertices on top of the edges?

$\endgroup$
6
  • $\begingroup$ To get a list of vertices from a Graph you could do VertexList[Graph[trans]]. $\endgroup$
    – Heike
    Jun 5, 2012 at 10:31
  • $\begingroup$ @Heike Thanks, I knew there had to be a simple way (I was looking under Property). $\endgroup$
    – Ajasja
    Jun 5, 2012 at 10:46
  • 1
    $\begingroup$ You might want to change the EdgeShapeFunction to something like EdgeShapeFunction -> ({Arrowheads[{{.03, 1}}], Arrow[#1, {.1, .15}]} &) to avoid too much traffic on top of vertices. $\endgroup$
    – kglr
    Jun 5, 2012 at 11:26
  • $\begingroup$ @kguler Thanks, your suggestion is very helpful (See edit). Is there perhaps a way to draw vertices on top of the edges? $\endgroup$
    – Ajasja
    Jun 5, 2012 at 15:11
  • $\begingroup$ Capsule is a difficult shape to deal with - calculation of the appropriate setbacks gets really complicated because it depends on the entire layout. But, for Circle the following works: EdgeShapeFunction -> ({Arrowheads[{{.03, 1}}], Arrow[#1, {.2 #2[[1]] /. vs, .2 #2[[2]] /. vs}]} &). $\endgroup$
    – kglr
    Jun 5, 2012 at 15:12

3 Answers 3

13
$\begingroup$

Using the setback argument of Arrow in the EdgeShapeFunction to account for different vertex sizes

EdgeShapeFunction -> 
      ({Arrowheads[{{.03, 1}}], Arrow[#1, {.2  #2[[1]] /. vs, .2 #2[[2]] /. vs}]} &)];

takes care of the first problem. One way to approach the second problem (putting vertices on top) is to superimpose an edge-less version of g on top of g.

First get a slightly modified version of your original graph,

l = VertexList[Graph[trans]];
r = RandomReal[{0.5, 1}, Length[l]];
vs = Transpose@{l, r} /. List[a_, b_] -> Rule[a, b];
vls = Transpose@{l, r} /. List[a_, b_] -> Rule[a, Directive[Black, Bold, 16 b]];
rr = RandomReal[{0.1, 1}, Length[DeleteDuplicates@trans]]/100;
es = (First@# -> Directive[Thickness[Last@#], Opacity[.5]]) & /@ 
   Transpose@{DeleteDuplicates@trans, rr};
g = Graph[DeleteDuplicates@trans, 
    VertexLabels -> Placed["Name", Center], 
    VertexShapeFunction -> "Circle", VertexStyle -> {Opacity[1]}, 
    VertexSize -> vs, VertexLabelStyle -> vls, EdgeStyle -> es, 
    EdgeShapeFunction -> 
      ({Arrowheads[{{.03, 1}}], Arrow[#1, {.2  #2[[1]] /. vs, .2 #2[[2]] /. vs}]} &)];

Then get an edge-less version of g using

Fold[SetProperty[{#1, #2}, EdgeStyle -> Opacity[0]] &, g, EdgeList[g]]

Finally, Show the two graphs.

All three graphs in a column:

 Column[{g, 
  Fold[SetProperty[{#1, #2}, EdgeStyle -> Opacity[0]] &, g, EdgeList[g]], 
  Show[g, Fold[SetProperty[{#1, #2}, EdgeStyle -> Opacity[0]] &, g, EdgeList[g]]]}]

enter image description here

EDIT: Perhaps, you do not want the edges to be completely occluded by the vertices. In that case, we can play with combinations of the opacity settings of the edges and vertex styles to make vertices look less cluttered. For example, using

es = (First@# -> Directive[Thickness[Last@#], Opacity[.3]]) & /@ 
     Transpose@{DeleteDuplicates@trans, rr};
vstyl = (Sequence @@ {# -> Hue@{.6 # /. vs}} & /@ l);

(where I modified the opacity setting in es to 3 and defined a vertex style setting that uses the values in vs to determined the vertex color), in

 Graph[DeleteDuplicates@trans, VertexLabels -> Placed["Name", Center], 
  VertexShapeFunction -> "Circle", VertexSize -> vs, 
  VertexStyle -> vstyl, VertexLabelStyle -> vls, EdgeStyle -> es, 
  EdgeShapeFunction -> 
   ({Arrowheads[{{.03, 1}}], Arrow[#1, {.2  #2[[1]] /. vs, .2 #2[[2]] /. vs}]} &),
 ImageSize -> 600]

gives

enter image description here

Another alternative is to use styles for the vertices in HighlightGraph as follows:

 HighlightGraph[g, {#, Style[#, {Opacity[1], Hue@{.2  # /. vs}}]} & /@ VertexList[g]]

which gives:

enter image description here

$\endgroup$
2
  • $\begingroup$ Thanks! Got something which is quite OK. Are there any options to make the edges go around vertices (like in LayeredGraphPlot)? But this should probably be a whole new question then ;) $\endgroup$
    – Ajasja
    Jun 12, 2012 at 12:11
  • $\begingroup$ @Ajasja,thank you for the accept. And, yes that would be an interesting new question indeed:) $\endgroup$
    – kglr
    Jun 12, 2012 at 19:20
13
$\begingroup$

Since I already have this code, I'm going to show an example using GraphPlot and an adjacency graph based on a matrix nums where the links can take positive values other than 1, or zero. Rather than having two arrows for each link, I use line thickness to indicate the sum of the two link values, color to show the ratio of the net (difference between the two directions’ values) to gross (sum of the two link values), and the size of the arrowhead to show which is the stronger link direction.

As kguler noted in comments, EdgeRenderingFunction (or in the case of Graph rather than GraphPlot, EdgeShapeFunction) can be specified so that the arrowheads don’t overlap the vertex.

GraphPlot[nums, DirectedEdges -> True, 
 VertexRenderingFunction -> ({White, EdgeForm[Black], Disk[#, .04], 
     Black, Text[names[[#2]], #1]} &), 
 EdgeRenderingFunction -> ({AbsoluteThickness[(nums[[#2[[1]], #2[[
           2]]]] + nums[[#2[[2]], #2[[1]]]])/(0.08 maxthick)], 
     RGBColor[
      Abs[(nums[[#2[[1]], #2[[2]]]] - 
          nums[[#2[[2]], #2[[1]]]])]/(0.8 (nums[[#2[[1]], #2[[2]]]] + 
           nums[[#2[[2]], #2[[1]]]])), 0.1, 0.7], 
     Arrowheads[
      0.010 + 0.004 Sign@(nums[[#2[[1]], #2[[2]]]] - 
           nums[[#2[[2]], #2[[1]]]])], Arrow[#1, 0.05]} &), 
 VertexLabeling -> True, ImageSize -> 900, 
 PlotLabel -> Style["Plot Label", Bold, 14, FontFamily -> "Arial"], 
 ImagePadding -> 0, PlotRangePadding -> 0.02]

I've just labelled the vertices with names = Range[20] but you could use strings or whatever you like.

enter image description here

Here is an example with multiple arrows, where thickness, gray shade and arrowhead size all depend on the value of the element in the adjaceny matrix.

GraphPlot[Sign[nums], DirectedEdges -> True, MultiedgeStyle -> True, 
 VertexRenderingFunction -> ({White, EdgeForm[Black], Disk[#, .04], 
     Black, Text[names[[#2]], #1]} &), 
 EdgeRenderingFunction -> (With[{relexp = (nums[[#2[[1]], #2[[2]]]])/
        maxthick}, {AbsoluteThickness[relexp*20.], 
      RGBColor[relexp*0.8, relexp*0.8, relexp*0.8], 
      Arrowheads[0.06 relexp + 0.008], Arrow[#1, 0.05]}] &), 
 VertexLabeling -> True, ImageSize -> 900, 
 PlotLabel -> Style["Plot Heading", Bold, 14, FontFamily -> "Arial"], 
 ImagePadding -> 0, PlotRangePadding -> 0.02]

enter image description here

Setting a numerical value for the MultiEdgeStyle option can bring the arrow pairs closer together and tidy up the look considerably.

 GraphPlot[Sign[nums], DirectedEdges -> True, MultiedgeStyle -> 0.02, 
 VertexRenderingFunction -> ({White, EdgeForm[Black], Disk[#, .04], 
     Black, Text[names[[#2]], #1]} &), 
 EdgeRenderingFunction -> (With[{relexp = (nums[[#2[[1]], #2[[2]]]])/
        maxthick}, {AbsoluteThickness[relexp*20.], 
      RGBColor[relexp*0.8, relexp*0.8, relexp*0.8], 
      Arrowheads[0.06 relexp + 0.008], Arrow[#1, 0.05]}] &), 
 VertexLabeling -> True, ImageSize -> 700, ImagePadding -> 0, 
 PlotRangePadding -> 0.02]

enter image description here

$\endgroup$
4
$\begingroup$

To fix the arrowhead positioning, use

PerformanceGoal -> "Quality"

If I remove the EdgeShapeFunction and add the above option, your code produces this:

enter image description here

Using the latest version of IGraph/M (prerelease), you can also simplify and structure your styling code. We start by creating a graph with edge and vertex weights:

g = Graph[trans, VertexWeight -> r, EdgeWeight -> rr]

Then each IGEdgeMap and IGVertexMap operator sets one style property based on another one. We chain multiple operators for each styling step. You can use // instead of /*, but the Front End indents more nicely with /*.

Graph[g, (* set static style options *)  
   VertexShapeFunction -> "Capsule", 
   VertexLabels -> Placed["Name", Center],
   PerformanceGoal -> "Quality", ImageSize -> Large
  ] //
  IGEdgeMap[ (* scale edge thickness and arrowhead size based on edge weight *)   
   Directive[Thickness[#], Arrowheads[4 #]] &,
   EdgeStyle -> IGEdgeProp[EdgeWeight]
  ] /*  
  IGVertexMap[ (* scale vertices based on vertex weight *)
   # &,
   VertexSize -> IGVertexProp[VertexWeight]
  ] /*  
  IGVertexMap[ (* scale vertex labels based on vertex weight *)   
   FontSize -> 15 # &,
   VertexLabelStyle -> IGVertexProp[VertexWeight]
  ]

enter image description here

$\endgroup$
3
  • $\begingroup$ Graph[trans, VertexWeight -> r, EdgeWeight -> rr] don't work here.And is it just a prerelease version?It's mean it will be change or other upgrade in official version? $\endgroup$
    – yode
    May 4, 2017 at 19:03
  • $\begingroup$ @yode Yes. But for this particular feature, it is very unlikely that I would change the syntax. Otherwise I would not post these answers. Some of the other new features might change. There are also bugs (some old) which are still unfixed, and which I want to fix before I call it a final version. $\endgroup$
    – Szabolcs
    May 4, 2017 at 19:07
  • $\begingroup$ @yode The r and rr were taken from the OP. $\endgroup$
    – Szabolcs
    May 4, 2017 at 19:12

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.