22Format Mathics objects
33"""
44
5+ import random
56import networkx as nx
67
78
@@ -57,6 +58,87 @@ def format_output(obj, expr, format=None):
5758 return boxes
5859
5960
61+ def hierarchy_pos (G , root = None , width = 1.0 , vert_gap = 0.2 , vert_loc = 0 , xcenter = 0.5 ):
62+
63+ """
64+ From Joel's answer at https://stackoverflow.com/a/29597209/2966723.
65+ Licensed under Creative Commons Attribution-Share Alike
66+
67+ If the graph is a tree this will return the positions to plot this in a
68+ hierarchical layout.
69+
70+ G: the graph (must be a tree)
71+
72+ root: the root node of current branch
73+ - if the tree is directed and this is not given,
74+ the root will be found and used
75+ - if the tree is directed and this is given, then
76+ the positions will be just for the descendants of this node.
77+ - if the tree is undirected and not given,
78+ then a random choice will be used.
79+
80+ width: horizontal space allocated for this branch - avoids overlap with other branches
81+
82+ vert_gap: gap between levels of hierarchy
83+
84+ vert_loc: vertical location of root
85+
86+ xcenter: horizontal location of root
87+ """
88+ if not nx .is_tree (G ):
89+ raise TypeError ("cannot use hierarchy_pos on a graph that is not a tree" )
90+
91+ if root is None :
92+ if isinstance (G , nx .DiGraph ):
93+ root = next (
94+ iter (nx .topological_sort (G ))
95+ ) # allows back compatibility with nx version 1.11
96+ else :
97+ root = random .choice (list (G .nodes ))
98+
99+ def _hierarchy_pos (
100+ G , root , width = 1.0 , vert_gap = 0.2 , vert_loc = 0 , xcenter = 0.5 , pos = None , parent = None
101+ ):
102+ """
103+ see hierarchy_pos docstring for most arguments
104+
105+ pos: a dict saying where all nodes go if they have been assigned
106+ parent: parent of this branch. - only affects it if non-directed
107+
108+ """
109+
110+ if pos is None :
111+ pos = {root : (xcenter , vert_loc )}
112+ else :
113+ pos [root ] = (xcenter , vert_loc )
114+ children = list (G .neighbors (root ))
115+ if not isinstance (G , nx .DiGraph ) and parent is not None :
116+ children .remove (parent )
117+ if len (children ) != 0 :
118+ dx = width / len (children )
119+ nextx = xcenter - width / 2 - dx / 2
120+ for child in children :
121+ nextx += dx
122+ pos = _hierarchy_pos (
123+ G ,
124+ child ,
125+ width = dx ,
126+ vert_gap = vert_gap ,
127+ vert_loc = vert_loc - vert_gap ,
128+ xcenter = nextx ,
129+ pos = pos ,
130+ parent = root ,
131+ )
132+ return pos
133+
134+ return _hierarchy_pos (G , root , width , vert_gap , vert_loc , xcenter )
135+
136+
137+ def tree_layout (G ):
138+ root = G .root if hasattr (G , "root" ) else None
139+ return hierarchy_pos (G , root = root )
140+
141+
60142NETWORKX_LAYOUTS = {
61143 "circular" : nx .circular_layout ,
62144 "multipartite" : nx .multipartite_layout ,
@@ -65,7 +147,7 @@ def format_output(obj, expr, format=None):
65147 "shell" : nx .shell_layout ,
66148 "spectral" : nx .spectral_layout ,
67149 "spring" : nx .spring_layout ,
68- "tree" : nx . planar_layout ,
150+ "tree" : tree_layout ,
69151}
70152
71153
@@ -82,6 +164,8 @@ def format_graph(G, options):
82164 if not isinstance (plot_theme , str ):
83165 plot_theme = plot_theme .get_string_value ()
84166 layout_fn = NETWORKX_LAYOUTS .get (plot_theme , None )
167+ else :
168+ layout_fn = None
85169
86170 if layout_fn :
87171 nx .draw (G , pos = layout_fn (G ), with_labels = vertex_labeling )
0 commit comments