J'essaie d'afficher un graphe arborescent de ma hiérarchie de classe à l'aide denetworkx.
je l'ai tout tracé correctement, et il affiche fine . Mais en tant que graphique circulaire avec des bords croisés, il s’agit d’une hiérarchie pure, et il me semble que je devrais pouvoir l’afficher sous forme d’arbre.
J'ai beaucoup cherché dans Google, et chaque solution proposée implique l'utilisation depygraphviz
... mais PyGraphviz ne fonctionne pas avec Python 3 (documentation du site pygraphviz) .
Quelqu'un a-t-il pu obtenir un graphique en arborescence dans Python 3?
edit (19 janv. 2019) J'ai mis à jour le code pour qu'il soit plus robuste: il fonctionne désormais sans modification pour les graphes dirigés et non dirigés, il n'est plus nécessaire que l'utilisateur spécifie la racine et il vérifie que le graphe est un arbre avant son exécution (sans le test, il aurait une récursion infinie - voir la réponse de user2479115 pour une manière de gérer les non-arbres).
edit (27 août 2018) Si vous voulez créer un tracé avec les noeuds apparaissant comme des anneaux autour du noeud racine, le code en bas à droite montre une modification simple pour ce faire
edit (17 sept. 2017) Je crois que le problème de pygraphviz qu'avait OP devait être résolu maintenant. Donc, pygraphviz est probablement une meilleure solution que celle que j'ai ci-dessous.
Voici un programme récursif simple pour définir les positions. La récurrence a lieu dans _hierarchy_pos
, appelé par hierarchy_pos
. Le rôle principal de hierarcy_pos
consiste à effectuer quelques tests pour s'assurer que le graphique est approprié avant d'entrer la récursivité:
import networkx as nx
import random
def hierarchy_pos(G, root=None, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5):
'''
From Joel's answer at https://stackoverflow.com/a/29597209/2966723
If the graph is a tree this will return the positions to plot this in a
hierarchical layout.
G: the graph (must be a tree)
root: the root node of current branch
- if the tree is directed and this is not given, the root will be found and used
- if the tree is directed and this is given, then the positions will be just for the descendants of this node.
- if the tree is undirected and not given, then a random choice will be used.
width: horizontal space allocated for this branch - avoids overlap with other branches
vert_gap: gap between levels of hierarchy
vert_loc: vertical location of root
xcenter: horizontal location of root
'''
if not nx.is_tree(G):
raise TypeError('cannot use hierarchy_pos on a graph that is not a tree')
if root is None:
if isinstance(G, nx.DiGraph):
root = next(iter(nx.topological_sort(G))) #allows back compatibility with nx version 1.11
else:
root = random.choice(list(G.nodes))
def _hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5, pos = None, parent = None):
'''
see hierarchy_pos docstring for most arguments
pos: a dict saying where all nodes go if they have been assigned
parent: parent of this branch. - only affects it if non-directed
'''
if pos is None:
pos = {root:(xcenter,vert_loc)}
else:
pos[root] = (xcenter, vert_loc)
children = list(G.neighbors(root))
if not isinstance(G, nx.DiGraph) and parent is not None:
children.remove(parent)
if len(children)!=0:
dx = width/len(children)
nextx = xcenter - width/2 - dx/2
for child in children:
nextx += dx
pos = _hierarchy_pos(G,child, width = dx, vert_gap = vert_gap,
vert_loc = vert_loc-vert_gap, xcenter=nextx,
pos=pos, parent = root)
return pos
return _hierarchy_pos(G, root, width, vert_gap, vert_loc, xcenter)
et un exemple d'utilisation:
import matplotlib.pyplot as plt
import networkx as nx
G=nx.Graph()
G.add_edges_from([(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9), (4,10),
(5,11), (5,12), (6,13)])
pos = hierarchy_pos(G,1)
nx.draw(G, pos=pos, with_labels=True)
plt.savefig('hierarchy.png')
Idéalement, cela devrait redimensionner la séparation horizontale en fonction de la largeur des éléments situés en dessous. Je ne tente pas cela maintenant.
Expansion radiale
Disons que vous voulez que l'intrigue ressemble à ceci:
Voici le code pour cela:
pos = hierarchy_pos(G, 0, width = 2*math.pi, xcenter=0)
new_pos = {u:(r*math.cos(theta),r*math.sin(theta)) for u, (theta, r) in pos.items()}
nx.draw(G, pos=new_pos, node_size = 50)
nx.draw_networkx_nodes(G, pos=new_pos, nodelist = [0], node_color = 'blue', node_size = 200)
edit - merci à Deepak Saini pour avoir signalé une erreur qui apparaissait auparavant dans les graphes orientés
Voici une solution pour les grands arbres. C'est une modification de l'approche récursive de Joel qui espace uniformément les nœuds à chaque niveau.
def hierarchy_pos(G, root, levels=None, width=1., height=1.):
'''If there is a cycle that is reachable from root, then this will see infinite recursion.
G: the graph
root: the root node
levels: a dictionary
key: level number (starting from 0)
value: number of nodes in this level
width: horizontal space allocated for drawing
height: vertical space allocated for drawing'''
TOTAL = "total"
CURRENT = "current"
def make_levels(levels, node=root, currentLevel=0, parent=None):
"""Compute the number of nodes for each level
"""
if not currentLevel in levels:
levels[currentLevel] = {TOTAL : 0, CURRENT : 0}
levels[currentLevel][TOTAL] += 1
neighbors = G.neighbors(node)
for neighbor in neighbors:
if not neighbor == parent:
levels = make_levels(levels, neighbor, currentLevel + 1, node)
return levels
def make_pos(pos, node=root, currentLevel=0, parent=None, vert_loc=0):
dx = 1/levels[currentLevel][TOTAL]
left = dx/2
pos[node] = ((left + dx*levels[currentLevel][CURRENT])*width, vert_loc)
levels[currentLevel][CURRENT] += 1
neighbors = G.neighbors(node)
for neighbor in neighbors:
if not neighbor == parent:
pos = make_pos(pos, neighbor, currentLevel + 1, node, vert_loc-vert_gap)
return pos
if levels is None:
levels = make_levels({})
else:
levels = {l:{TOTAL: levels[l], CURRENT:0} for l in levels}
vert_gap = height / (max([l for l in levels])+1)
return make_pos({})
L'exemple de Joel ressemblera à ceci:
Et ceci est un graphique plus complexe (rendu en utilisant plotly):
J'ai légèrement modifié pour qu'il ne soit pas recurse à l'infini.
import networkx as nx
def hierarchy_pos(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5 ):
'''If there is a cycle that is reachable from root, then result will not be a hierarchy.
G: the graph
root: the root node of current branch
width: horizontal space allocated for this branch - avoids overlap with other branches
vert_gap: gap between levels of hierarchy
vert_loc: vertical location of root
xcenter: horizontal location of root
'''
def h_recur(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5,
pos = None, parent = None, parsed = [] ):
if(root not in parsed):
parsed.append(root)
if pos == None:
pos = {root:(xcenter,vert_loc)}
else:
pos[root] = (xcenter, vert_loc)
neighbors = G.neighbors(root)
if parent != None:
neighbors.remove(parent)
if len(neighbors)!=0:
dx = width/len(neighbors)
nextx = xcenter - width/2 - dx/2
for neighbor in neighbors:
nextx += dx
pos = h_recur(G,neighbor, width = dx, vert_gap = vert_gap,
vert_loc = vert_loc-vert_gap, xcenter=nextx, pos=pos,
parent = root, parsed = parsed)
return pos
return h_recur(G, root, width=1., vert_gap = 0.2, vert_loc = 0, xcenter = 0.5)
Le moyen le plus simple d'obtenir un bel affichage graphique sous Python 2 ou 3 sans PyGraphviz est d'utiliser PyDot ( https://pypi.python.org/pypi/pydot ). Alors que PyGraphviz fournit une interface à l'ensemble de Graphviz, PyDot ne fournit qu'une interface à l'outil Point de Graphviz, qui est le seul dont vous avez besoin si vous recherchez un graphique/arbre hiérarchique. Si vous souhaitez créer votre graphique dans NetworkX plutôt que dans PyDot, vous pouvez utiliser NetworkX pour exporter un graphique PyDot, comme suit:
import networkx as nx
g=nx.DiGraph()
g.add_edges_from([(1,2), (1,3), (1,4), (2,5), (2,6), (2,7), (3,8), (3,9),
(4,10), (5,11), (5,12), (6,13)])
p=nx.drawing.nx_pydot.to_pydot(g)
p.write_png('example.png')
Notez que Graphviz et PyDot doivent être installés pour que ce qui précède fonctionne correctement.
Avertissement: des problèmes ont été rencontrés lors de l’utilisation de PyDot pour dessiner des graphiques avec des dictionnaires d’attributs de noeud exportés à partir de NetworkX. Parfois, les dictionnaires semblent être exportés avec des guillemets manquants dans les chaînes, ce qui provoque le blocage de la méthode write
. Cela peut être évité en laissant de côté les dictionnaires.
Pour un graphe orienté, Puisque voisins (x) n'incluent que les successeurs (x), vous devez donc supprimer les lignes:
if parent != None:
neighbors.remove(parent)
En outre, une meilleure option serait la suivante:
pos=nx.graphviz_layout(G,prog='dot')