1
+ # For converting data from various forms to other forms
2
+ from streamlit_agraph import agraph , Node , Edge , Config
3
+ import logging
4
+ import json
5
+
6
+ def random_coordinates_for (
7
+ number : int ):
8
+ import numpy as np
9
+
10
+ # Generate random coordinates
11
+ min_range = 0
12
+ max_width = number * 300
13
+ x_values = np .random .uniform (min_range , max_width , number )
14
+ y_values = np .random .uniform (min_range , max_width , number )
15
+
16
+ # Create list of (x, y) coordinates
17
+ coordinates = list (zip (x_values , y_values ))
18
+ return coordinates
19
+
20
+
21
+ def agraph_data_from_response (response : str )-> tuple [any , any , any ]:
22
+ # Returns agraph compatible nodes, edges, and config
23
+ logging .debug (f'Response: { response } ' )
24
+ # Response will be a list of 3 item tuples
25
+ try :
26
+ answers = json .loads (response )
27
+ except Exception as e :
28
+ logging .error (e )
29
+ return None , None , None
30
+ if isinstance (answers , list ) == False :
31
+ logging .error (f'Response could not be converted to list, got { type (answers )} instead.' )
32
+ return None , None , None
33
+ nodes = []
34
+ # node_labels = []
35
+ edges = []
36
+ for idx , item in enumerate (answers ):
37
+ if item is None or len (item ) != 3 :
38
+ continue
39
+ n1_label = item [0 ]
40
+ r = item [1 ]
41
+ n2_label = item [2 ]
42
+
43
+ # We only want to add agraph nodes with the same label once in our return
44
+
45
+ # Gross but works
46
+ add_n1 = True
47
+ for node in nodes :
48
+ if node .label == n1_label :
49
+ add_n1 = False
50
+ if add_n1 :
51
+ nodes .append (Node (id = n1_label , label = n1_label ))
52
+
53
+ add_n2 = True
54
+ for node in nodes :
55
+ if node .label == n2_label :
56
+ add_n2 = False
57
+ if add_n2 :
58
+ nodes .append (Node (id = n2_label , label = n2_label ))
59
+
60
+ # agraph requires source and target ids to use what we consider labels
61
+ edge = Edge (source = n1_label , target = n2_label , label = r )
62
+ edges .append (edge )
63
+ config = Config (
64
+ width = 800 ,
65
+ height = 800 ,
66
+ backgroundColor = "#000000" ,
67
+ directed = True )
68
+ return (nodes , edges , config )
69
+
70
+
71
+ def convert_agraph_nodes_to_arrows_nodes (
72
+ agraph_nodes : list
73
+ )-> list [dict ]:
74
+ # Convert agraph nodes to arrows nodes
75
+ arrows_nodes = []
76
+
77
+ # Generate random coordinates to init new arrows nodes with - since we can't extract the location data from agraph
78
+ coordinates = random_coordinates_for (len (agraph_nodes ))
79
+
80
+ for nidx , node in enumerate (agraph_nodes ):
81
+ new_node = convert_agraph_node_to_arrows_node (
82
+ nidx , node , coordinates [nidx ][0 ], coordinates [nidx ][1 ])
83
+ arrows_nodes .append (new_node )
84
+ return arrows_nodes
85
+
86
+ def convert_agraph_node_to_arrows_node (
87
+ idx ,
88
+ node ,
89
+ x ,
90
+ y ):
91
+ # Convert agraph node to arrows node
92
+ arrows_node = {
93
+ "id" : f'n{ idx + 1 } ' ,
94
+ "caption" : node .label ,
95
+ "position" : {
96
+ "x" : x ,
97
+ "y" : y ,
98
+ },
99
+ "labels" :[],
100
+ "style" : {},
101
+ "properties" : {}
102
+ }
103
+ return arrows_node
104
+
105
+ def convert_agraph_edge_to_arrows_relationship (
106
+ idx ,
107
+ edge ,
108
+ arrows_nodes : list ):
109
+ # Example: {'source': 'People', 'from': 'People', 'to': 'Cars', 'color': '#F7A7A6', 'label': 'DRIVE'}
110
+ source_node_label = edge .source
111
+ target_node_label = edge .to
112
+ source_node_id = None
113
+ target_node_id = None
114
+
115
+ for node in arrows_nodes :
116
+ if node ['caption' ] == source_node_label :
117
+ source_node_id = node ['id' ]
118
+ if node ['caption' ] == target_node_label :
119
+ target_node_id = node ['id' ]
120
+
121
+ if source_node_id is None or target_node_id is None :
122
+ node_info = [node .__dict__ for node in arrows_nodes ]
123
+ logging .error (f'Could not find source or target node for edge { edge .__dict__ } from nodes: { node_info } ' )
124
+ return None
125
+ edge_type = edge .label
126
+ arrows_relationship = {
127
+ "id" : f'n{ idx + 1 } ' ,
128
+ "fromId" : source_node_id ,
129
+ "toId" : target_node_id ,
130
+ "type" : edge_type ,
131
+ "properties" : {},
132
+ "style" : {}
133
+ }
134
+ return arrows_relationship
135
+
136
+ def convert_agraph_to_arrows (agraph_nodes , agraph_edges ):
137
+ arrows_nodes = convert_agraph_nodes_to_arrows_nodes (agraph_nodes )
138
+
139
+ arrows_relationships = []
140
+ for eidx , edge in enumerate (agraph_edges ):
141
+ new_relationship = convert_agraph_edge_to_arrows_relationship (eidx , edge , arrows_nodes = arrows_nodes )
142
+ arrows_relationships .append (new_relationship )
143
+ arrows_json = {
144
+ "nodes" : arrows_nodes ,
145
+ "relationships" : arrows_relationships ,
146
+ "style" : {
147
+ "font-family" : "sans-serif" ,
148
+ "background-color" : "#ffffff" ,
149
+ "background-image" : "" ,
150
+ "background-size" : "100%" ,
151
+ "node-color" : "#ffffff" ,
152
+ "border-width" : 4 ,
153
+ "border-color" : "#000000" ,
154
+ "radius" : 50 ,
155
+ "node-padding" : 5 ,
156
+ "node-margin" : 2 ,
157
+ "outside-position" : "auto" ,
158
+ "node-icon-image" : "" ,
159
+ "node-background-image" : "" ,
160
+ "icon-position" : "inside" ,
161
+ "icon-size" : 64 ,
162
+ "caption-position" : "inside" ,
163
+ "caption-max-width" : 200 ,
164
+ "caption-color" : "#000000" ,
165
+ "caption-font-size" : 50 ,
166
+ "caption-font-weight" : "normal" ,
167
+ "label-position" : "inside" ,
168
+ "label-display" : "pill" ,
169
+ "label-color" : "#000000" ,
170
+ "label-background-color" : "#ffffff" ,
171
+ "label-border-color" : "#000000" ,
172
+ "label-border-width" : 4 ,
173
+ "label-font-size" : 40 ,
174
+ "label-padding" : 5 ,
175
+ "label-margin" : 4 ,
176
+ "directionality" : "directed" ,
177
+ "detail-position" : "inline" ,
178
+ "detail-orientation" : "parallel" ,
179
+ "arrow-width" : 5 ,
180
+ "arrow-color" : "#000000" ,
181
+ "margin-start" : 5 ,
182
+ "margin-end" : 5 ,
183
+ "margin-peer" : 20 ,
184
+ "attachment-start" : "normal" ,
185
+ "attachment-end" : "normal" ,
186
+ "relationship-icon-image" : "" ,
187
+ "type-color" : "#000000" ,
188
+ "type-background-color" : "#ffffff" ,
189
+ "type-border-color" : "#000000" ,
190
+ "type-border-width" : 0 ,
191
+ "type-font-size" : 16 ,
192
+ "type-padding" : 5 ,
193
+ "property-position" : "outside" ,
194
+ "property-alignment" : "colon" ,
195
+ "property-color" : "#000000" ,
196
+ "property-font-size" : 16 ,
197
+ "property-font-weight" : "normal"
198
+ }
199
+ }
200
+ return arrows_json
201
+
0 commit comments