33
44@author: jv
55'''
6+ from collections .abc import Iterable
7+ from typing import Any , List , Union
8+
69import graphviz
10+
11+ from gremlin_python .process .anonymous_traversal import GraphTraversalSource
12+ from gremlin_python .process .graph_traversal import GraphTraversal
13+ from gremlin_python .process .traversal import T
14+ from gremlin_python .structure .graph import Vertex , Edge , Path
15+
16+ from aenum import Enum
17+
18+
719class GremlinDraw :
8- @classmethod
9- def show (cls ,g ,title :str = "Gremlin" , v_limit :int = 10 , e_limit :int = 10 ):
10- G = graphviz .Digraph (title ,format = "pdf" )
20+ @staticmethod
21+ def __draw_vertex (digraph : graphviz .Digraph , g : GraphTraversalSource , vertex : Vertex ) -> graphviz .Digraph :
22+ """
23+ draw a single given vertex
24+ """
25+ # developer note: see https://github.com/apache/tinkerpop/blob/master/gremlin-python/src/main/python/gremlin_python/structure/graph.py#LL58C23-L58C23
26+ # when gremlin-python 3.7.0 is released, the following code can be improved (get the properties using vertex.properties)
27+ # then, g can also be removed as a parameter
28+
29+ # get the properties of the vertex
30+ kvp_list = list (next (g .V (vertex ).element_map ()).items ())
31+ # non-proerty items are of type aenum
32+ properties = [item for item in kvp_list if not isinstance (item [0 ], Enum )]
33+ assert len (properties ) == len (kvp_list ) - 2 # ID and label are not properties
34+
35+ properties_label = "\n " .join (f"{ key } : { value } " for key , value in properties )
36+
37+ # draw the vertex
38+ digraph .node (
39+ name = str (vertex .id ),
40+ label = f"{ str (vertex .id )} \n { vertex .label } \n { '─' * 5 } \n { properties_label } " ,
41+ fillcolor = "#ADE1FE" ,
42+ style = "filled" ,
43+ fontname = "arial"
44+ )
45+
46+ return digraph
47+
48+
49+ @staticmethod
50+ def __draw_edge (digraph : graphviz .Digraph , g : GraphTraversalSource , edge : Edge ) -> graphviz .Digraph :
51+ """
52+ draw a single given edge
53+ """
54+ # developer note: see https://github.com/apache/tinkerpop/blob/master/gremlin-python/src/main/python/gremlin_python/structure/graph.py#L66
55+ # when gremlin-python 3.7.0 is released, the following code can be improved (get the properties using edge.properties)
56+ # then, g can also be removed as a parameter
57+
58+ # get the properties of the edge
59+ #kvp_list = list(next(g.E(edge).element_map()).items())
60+ # Workaround, because the above line does not work due to inconsistencies / bugs in the gremlin-python library
61+ kvp_list = [edge_element_map for edge_element_map in g .E ().element_map ().to_list () if edge_element_map [T .id ] == edge .id ][0 ].items ()
62+ # non-proerty items are of type aenum
63+ properties = [item for item in kvp_list if not isinstance (item [0 ], Enum )]
64+ assert len (properties ) == len (kvp_list ) - 4 # ID, label, in, and out are not properties
65+
66+ properties_label = "\n " .join (f"{ key } : { value } " for key , value in properties )
67+
68+ # get the image of the edge by id
69+ in_vertex_id = edge .inV .id
70+ out_vertex_id = edge .outV .id
71+
72+ # draw the edge
73+ digraph .edge (
74+ tail_name = str (in_vertex_id ),
75+ head_name = str (out_vertex_id ),
76+ label = f"{ str (edge .id )} \n { edge .label } \n { '─' * 5 } \n { properties_label } " ,
77+ style = "setlinewidth(3)" ,
78+ fontname = "arial"
79+ )
80+
81+ return digraph
82+
83+
84+ @staticmethod
85+ def show (g : GraphTraversalSource , title :str = "Gremlin" , v_limit :int = 10 , e_limit :int = 10 ) -> graphviz .Digraph :
86+ """
87+ draw the given graph
88+ """
89+ G : graphviz .Digraph = graphviz .Digraph (title , format = "pdf" )
1190
1291 # draw vertices
13- vlist = g .V ().toList ()
92+ vlist = g .V ().to_list ()
1493 vlist = vlist [:v_limit ]
1594
1695 for v in vlist :
17- kvp_list = list (g .V (v ).elementMap ().next ().items ())
18- kvp_list .pop (0 ) # pop ID
19- kvp_list .pop (0 ) # pop label
20- vertex_properties = ""
21- for key ,val in kvp_list :
22- vertex_properties += "\n " + str (key ) + ": " + str (val );
23-
24- G .node (name = str (v .id ), label = str (v .id ) + "\n " + v .label + "\n " + '─' * 5 + vertex_properties , fillcolor = "#ADE1FE" , style = "filled" , fontname = "arial" )
25-
96+ G = GremlinDraw .__draw_vertex (G , g , v )
97+
2698 #draw edges
27- elist = g .E ().elementMap (). toList ()
99+ elist = g .E ().to_list ()
28100 elist = elist [:e_limit ]
29101
30102 for e in elist :
31- kvp_list = list (e .items ())
32- kvp_list .pop (0 ) # pop ID
33- kvp_list .pop (0 ) # pop label
34- kvp_list .pop (0 ) # pop in
35- kvp_list .pop (0 ) # pop out
36- edge_properties = ""
37- for key ,val in kvp_list :
38- edge_properties += "\n " + str (key ) + ": " + str (val );
39-
40- # list(list(e.values())[2].values())[0]
41- # str(g.E(e).inV().next().id)
42- # str(g.E(e).outV().next().id)
43- # list(e.values())[0]
44-
45- G .edge (tail_name = str (list (list (e .values ())[3 ].values ())[0 ]), head_name = str (list (list (e .values ())[2 ].values ())[0 ]), style = "setlinewidth(3)" , label = str (list (e .values ())[0 ]) + "\n " + str (list (e .values ())[1 ]) + "\n " + '─' * 5 + edge_properties , fontname = "arial" )
46- return G
103+ G = GremlinDraw .__draw_edge (G , g , e )
104+
105+ return G
106+
107+ @staticmethod
108+ def show_graph_traversal (g : GraphTraversalSource , gt : Union [GraphTraversal , Any ], title : str = "Gremlin" ) -> graphviz .Digraph :
109+ """
110+ draw the given graph traversal
111+ """
112+ # developer note: when moving the minium supported version up to 3.10, the following code can be greatly improved by using match statements
113+
114+ G : graphviz .Digraph = graphviz .Digraph (title , format = "pdf" )
115+
116+ worklist : List [Any ] = gt .to_list () if isinstance (gt , GraphTraversal ) else list (gt ) if isinstance (gt , Iterable ) else [gt ]
117+
118+ while len (worklist ) > 0 :
119+ # move any vertices to the front of the worklist (draw them first)
120+ worklist = [item for item in worklist if not isinstance (item , Vertex )] + [item for item in worklist if isinstance (item , Vertex )]
121+
122+ result = worklist .pop (0 )
123+
124+ if isinstance (result , Vertex ):
125+ G = GremlinDraw .__draw_vertex (G , g , result )
126+ elif isinstance (result , Edge ):
127+ G = GremlinDraw .__draw_edge (G , g , result )
128+ elif isinstance (result , Path ):
129+ for item in result .objects :
130+ worklist .append (item )
131+ elif isinstance (result , dict ):
132+ if T .id in result :
133+ # check if the id is a vertex or an edge
134+ if g .V (result [T .id ]).hasNext ():
135+ G = GremlinDraw .__draw_vertex (G , g , next (g .V (result [T .id ])))
136+ elif g .E (result [T .id ]).hasNext ():
137+ G = GremlinDraw .__draw_edge (G , g , g .E (result [T .id ]).next ())
138+ else :
139+ #raise Exception("id not found")
140+ pass # silent skip
141+ else :
142+ #raise Exception("id not found")
143+ pass # silent skip
144+ else :
145+ #raise Exception(f"unknown type: {type(result)}")
146+ pass # silent skip
147+
148+ return G
149+
150+
0 commit comments