-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGraph.py
390 lines (370 loc) · 17.1 KB
/
Graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
import webbrowser
import networkx as nx
from pyvis.network import Network
from utils import *
import random as rd
class Graph:
data = [] # data from Json used to initialize the graph
Metabolites = [] # Metabolites added from search
Reaction = [] # Reaction added from search
compartment = []
search_compartment = []
nodes_metabolites = []
nodes_reactions = []
edges = []
meta_keyword = [] # liste of keyword during search
reac_keyword = [] # liste temp pour garder les catégories pour reactions et metabolites
cofactors = ["CDP-CHOLINE_c", "CMP_c", "PROTON_c", "PPI_c", "PROTOHEME_c", "WATER_c",
"OXYGEN-MOLECULE_c", "CARBON-DIOXIDE_c", "NADPH_c", "NADP_c", "CMP_CCO-RGH-ER-LUM",
"PROTON_CCO-RGH-ER-LUM", "Pi_c", "AMP_c", "Acceptor_c", "CO-A_c", "ATP_c", "ADP_c", "GDP_c",
"GTP_c", "MG+2_c", "PROTON_e", "OXYGEN-MOLECULE_m", "WATER_m", "CARBON-MONOXIDE_c", "NAD_c", "NADH_c",
"Donor-H2_c"]
G = nx.MultiDiGraph()
corresp_dict_reac = {}
reversed_corresp_dict_reac = {}
def update_data(self, file):
"""
This is used to update the file used to draw graph in the app. By clearing data from the previous file and
update the data from the new file
:param file: path to the file to be used. (Selected in the app)
"""
self.Metabolites.clear()
self.Reaction.clear()
self.edges.clear()
self.nodes_metabolites.clear()
self.nodes_reactions.clear()
self.data = load_json(file)
get_metacyc_ids(self.data, os.path.dirname(file))
self.corresp_dict_reac, self.reversed_corresp_dict_reac = build_correspondence_dict(
os.path.dirname(file) + "\\metacyc_ids.tsv")
for comp in self.data["compartments"]:
comp = cobra_compatibility(comp)
self.compartment.append(comp)
def __init__(self, file=""):
if file != "":
self.data = load_json(file)
for comp in self.data["compartments"]:
comp = cobra_compatibility(comp)
self.compartment.append(comp)
get_metacyc_ids(self.data, os.path.dirname(file))
else:
self.data = []
# clear data from current instance, used for GUI implementation where only one graph instance is used.
def clear_data(self):
"""
Clear all the data used to create graph.
:return:
"""
self.Metabolites.clear()
self.Reaction.clear()
self.edges.clear()
self.nodes_reactions.clear()
self.nodes_metabolites.clear()
self.meta_keyword.clear()
self.reac_keyword.clear()
self.search_compartment.clear()
def meta_keyword_update(self, keyword):
"""
Fills the list containing the current search for metabolites selected in the app. Clicking on a new metabolite
adds it to the list. Clicking on the metabolite already searched removes it from the list.
:param keyword: ID of the metabolite (selected in app)
:return: Boolean return used to only do the first if statement if condition is fullfilled.
(Return vlaue is not used)
"""
keyword = cobra_compatibility(keyword)
if keyword not in self.meta_keyword:
self.meta_keyword.append(keyword)
return True
if keyword in self.meta_keyword:
self.meta_keyword.remove(keyword)
return False
def reac_keyword_update(self, keyword):
"""
Fills the list containing the current search for reactions selected in the app. Clicking on a new reaction
adds it to the list. Clicking on the reaction already searched removes it from the list.
:param keyword: ID of the reaction (selected in app)
:return: Boolean return used to only do the first if statement if condition is fullfilled.
(Return value is not used)
"""
keyword = cobra_compatibility(keyword)
if keyword not in self.reac_keyword:
self.reac_keyword.append(keyword)
return True
if keyword in self.reac_keyword:
self.reac_keyword.remove(keyword)
return False
def compartment_update(self, keyword):
"""
Fills the list containing the current search for reactions selected in the app. Clicking on a new reaction
adds it to the list. Clicking on the reaction already searched removes it from the list.
:param keyword: ID of the compartment (selected in app)
:return: Boolean return used to only do the first if statement if condition is fullfilled.
(Return value is not used)
"""
keyword = cobra_compatibility(keyword)
if keyword not in self.search_compartment:
self.search_compartment.append(keyword)
return True
if keyword in self.search_compartment:
self.search_compartment.remove(keyword)
return False
def create_nodes_metabolites(self, data):
"""
Add graphic attributes to the nodes that will be used for a better visualisation. Different options are selected
whether the metabolite is a cofactor or not.
:param data: Either the data relevant to user search, or the whole json file loaded in the app
if no search were made.
"""
for item in data:
if item["id"] in self.cofactors:
item["size"] = 10 # Size of the node
item[
"group"] = "cofactors" # Group of the node (to differentiate metabolites from reactions and cofactors)
item["title"] = item["id"] # Message displayed when node is hoverede by mouse
item["mass"] = 0.5
item["physics"] = False
item["color"] = "#26A4DCFF"
item["border"] = "#000000"
else:
item["size"] = 20 # Size of the node
item[
"group"] = "metabolites" # Group of the node (to differentiate metabolites from reactions and cofactors)
item["title"] = item["id"] # Message displayed when node is hoverede by mouse
item["mass"] = 2
item["color"] = "#EE2121FF"
if "name" in item:
del item["name"]
self.nodes_metabolites.append((item['id'], item))
def create_nodes_reactions(self, data):
"""
Add graphic attributes to the nodes that will be used for a better visualisation.
:param data: Either the data relevant to user search, or the whole json file loaded in the app
if no search were made.
:param data:
:return:
"""
for item in data:
# test to add node size scaling depending on a specified value.
# It works but override the size attribute of the node.
# rand = rd.uniform(0.5,1.0)
# item["value"] = rand
item["size"] = 35
item["group"] = "reactions"
item["shape"] = "diamond"
item["mass"] = 5
item["title"] = item["gene_reaction_rule"].replace(" or ", " <br> ")#trick to display the gene reaction rule over multiple line for the web browser visualisation.
item["color"] = "#BFE117FF"
if "name" in item:
del item["name"]
self.nodes_reactions.append((item['id'], item))
def create_edges(self, cofactor):
"""
Function to create edges. Edges are 2-tuple containing the beginning node and the arrival node.
It looks through the metabolites invovled in the reaction and the stoechiometry.
If the metabolite is produced, its stoechiometry is positive, thus the edge is created with the pattern :
(reaction, metabolite).
If the metabolite is consumed, its stoechiometry is negative, thus the edge is created with the pattern :
(metabolite, reaction).
:param cofactor: Boolean : True, if cofactors are to be included in the graph. False elsewise. (selected in app)
"""
for reaction in self.Reaction:
for metabolite, stoech in reaction["metabolites"].items():
if not cofactor:
if metabolite not in self.cofactors:
if stoech > 0:
self.edges.append((reaction["id"], metabolite))
if stoech < 0:
self.edges.append((metabolite, reaction["id"]))
else:
if stoech > 0:
self.edges.append((reaction["id"], metabolite))
if stoech < 0:
self.edges.append((metabolite, reaction["id"]))
# search for metabolites based on list of metabo ID
# add all the reactions connected to the searched metabolites and all the metabolites associated with the reactions.
def search_metabolites(self, cofactor):
"""
Loop through the list of metabolites added in the search list. It then adds the reaction were each metabolite
appear and all the metabolites involved in these reactions to the Reactions and Metabolites list respectively.
These lists will be used to create the graph.
:param cofactor: Boolean : True, if cofactors are to be included in the graph. False elsewise. (selected in app)
:return:
"""
if not self.meta_keyword:
return None
temp_meta = {}
for key in self.meta_keyword:
for reac in self.data["reactions"]:
if key in reac["metabolites"] and reac not in self.Reaction:
reac["id"] = self.reversed_corresp_dict_reac[reac["id"].strip("_")]
self.Reaction.append(reac)
temp_meta = reac["metabolites"]
for meta in self.data["metabolites"]:
if meta["id"] in temp_meta.keys() and meta not in self.Metabolites:
if not cofactor:
if meta["id"] not in self.cofactors:
self.Metabolites.append(meta)
else:
self.Metabolites.append(meta)
def search_reactions(self, cofactor):
"""
Loop through the reactions added in the search. Add the reactions to the Reactions list and the metabolitses
involved in them in the Metabolites list. These lists will be used to create the graph.
:param cofactor:
:return: Boolean : True, if cofactors are to be included in the graph. False elsewise. (selected in app)
"""
if not self.reac_keyword:
return None
temp_meta = {}
for key in self.reac_keyword:
for reac in self.data["reactions"]:
if reac["id"] == key and reac not in self.Reaction:
reac["id"] = self.reversed_corresp_dict_reac[reac["id"].strip("_")]
self.Reaction.append(reac)
temp_meta = reac["metabolites"]
for meta in self.data["metabolites"]:
if meta["id"] in temp_meta.keys() and meta not in self.Metabolites:
if not cofactor:
if meta["id"] not in self.cofactors:
self.Metabolites.append(meta)
else:
self.Metabolites.append(meta)
# ------------------ Function to save graphs ------------------- #
def save_graph_json(self, name):
"""
Save the metabolites and reactions that were used to create the graph visualised.
:param name: Name of the file to save.
"""
tot_dico = {}
meta = []
reac = []
for gene in self.Reaction:
del gene["group"]
del gene["size"]
del gene["title"]
for item in self.Metabolites:
del item["group"]
del item["size"]
del item["title"]
meta.append(item)
for item in self.Reaction:
reac.append(item)
tot_dico["metabolites"] = meta
tot_dico["reactions"] = reac
with open(name, 'w') as f:
f.write(json.dumps(tot_dico))
# ------------------ Functions to show graph ----------------------------- #
def create_Graph(self, name, cofactor, physics):
"""
First extract data from the full model to only add metabolites and reactions relevant to the search.
Then create the nodes and edges and use Networkx to generate a graph. Once graph is created, launch the function
using Pyvis to visualize the graph.
:param name: name of the html file created by pyvis to show the graph
:param cofactor: Boolean : True, if cofactors are to be included in the graph. False elsewise.(selected in app)
:param physics: int : Value determined the kind of physics wanted by the user to draw the graph. (selected in app)
:return:
"""
self.G.clear()
if self.meta_keyword or self.reac_keyword:
# if searched has been made.
self.search_metabolites(cofactor)
self.search_reactions(cofactor)
self.create_nodes_metabolites(self.Metabolites)
self.create_nodes_reactions(self.Reaction)
self.create_edges(cofactor)
self.G.add_nodes_from(self.nodes_metabolites)
self.G.add_nodes_from(self.nodes_reactions)
for start, finish in self.edges:
if start in self.cofactors or finish in self.cofactors:
self.G.add_edge(start, finish, physics=False, dashes=True)
else:
self.G.add_edge(start, finish, physics=True, width=3)
self.show_graph(name, physics)
else: # create graph from all JSON if no search was made
self.create_nodes_metabolites(self.data["metabolites"])
self.create_nodes_reactions(self.data["reactions"])
self.create_edges(self.data["reactions"], cofactor)
self.G.add_nodes_from(self.nodes_metabolites)
self.G.add_nodes_from(self.nodes_reactions)
self.G.add_edges_from(self.edges, physics=False)
self.show_graph(name, physics)
def show_graph(self, name, option):
"""
Create Pyvis network from the networkx graph created before, with several display options depending on user
demand.
:param name: string : Name of the html file saved containing the graph visualization.
:param option: int : Value determined the kind of physics wanted by the user to draw the graph. (selected in app)
"""
nt = Network("1000px", "1000px")
nt.set_template("PlantGEM_template.html")
nt.from_nx(self.G, show_edge_weights=False)
nt.toggle_hide_edges_on_drag(True)
nt.set_edge_smooth("vertical")
# Set_graphical options to try to have good representation of graph
if option == 1:
nt.set_options("""
var options = {
"nodes": {
"borderWidth": 4,
"borderWidthSelected": 5,
"color": {
"border": "rgba(0,0,0,1)"
}
},
"edges": {
"arrows": {
"to": {
"enabled": true,
"scaleFactor": 1.4
}
},
"color": {
"inherit": true
},
"selectionWidth": 5,
"smooth": {
"type": "vertical",
"forceDirection": "none"
}
},
"physics": {
"barnesHut": {
"gravitationalConstant": -1500
},
"minVelocity": 0.75
}
}""")
if option == 2:
nt.show_buttons(filter_=["physics", "nodes", "edges"])
if option == 3:
nt.set_options("""
var options = {
"nodes": {
"borderWidthSelected": 5
},
"edges": {
"selectionWidth": 5,
"arrows": {
"to": {
"enabled": true,
"scaleFactor": 1.4
}
},
"color": {
"inherit": true
},
"smooth": {
"forceDirection": "none"
}
},
"interaction": {
"hideEdgesOnDrag": true
},
"physics": {
"minVelocity": 0.75,
"enabled" : false
}
}
""")
nt.write_html(name)
webbrowser.open(name)