from FEV_KEGG.Graph import Models
import networkx.classes
import os
from enum import Enum
from FEV_KEGG.Graph.SubstanceGraphs import SubstanceEcGraph
import math
from FEV_KEGG import settings
LABEL_NAME = 'custom_label'
"""
Name of the attribute of the graph to be used for storing the label.
"""
COLOUR_NAME = 'colour'
"""
Name of the attribute of the graph to be used for storing the colour.
"""
DESCRIPTION_NAME = 'custom_description'
"""
Name of the attribute of the graph to be used for storing the description.
"""
ID_NAME = 'custom_id'
"""
Name of the attribute of the graph to be used for storing the id.
"""
REACTION_NAME = 'custom_reaction'
"""
Name of the attribute of the graph to be used for storing the reaction.
"""
MAJORITY_NAME = 'custom_majority'
"""
Name of the attribute of the graph to be used for storing the majority percentage.
"""
URL_NAME = 'URL'
"""
Name of the attribute of the graph to be used for storing the URL.
"""
[docs]def addLabelAttribute(nxGraph: networkx.classes.MultiGraph):
"""
Adds the "custom_label" attribute to each node and edge.
Add an attribute to nodes and edges called "custom_label" (see module variable :attr:`LABEL_NAME`) containing the string of the node's label, or the edge's key's label, repectively.
A label is defined as either the `.name` field, or if that is *None*, the id.
This is especially useful if the tool you import this file into does not regularly read the XML's id parameter. For example, Cytoscape does not for edges.
Parameters
----------
nxGraph : networkx.classes.MultiGraph
A NetworkX graph object.
"""
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError()
# add label to edges
edges = nxGraph.edges(keys = True)
attributeDict = dict()
for edge in edges:
try:
name = edge[2].name
except AttributeError:
name = None
attributeDict[edge] = name if name is not None else edge[2].__str__()
networkx.set_edge_attributes(nxGraph, attributeDict, LABEL_NAME)
# add label to nodes
nodes = nxGraph.nodes
attributeDict = dict()
for node in nodes:
try:
name = node.name
except AttributeError:
name = None
attributeDict[node] = name if name is not None else node.__str__()
networkx.set_node_attributes(nxGraph, attributeDict, LABEL_NAME)
[docs]def addIdAttribute(nxGraph: networkx.classes.MultiGraph):
"""
Adds the "custom_id" attribute to each node and edge.
Add an attribute to nodes and edges called "custom_id" (see module variable :attr:`ID_NAME`) containing the string of the node's id, or the edge's key's id, repectively.
Parameters
----------
nxGraph : networkx.classes.MultiGraph
A NetworkX graph object.
"""
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError()
# add label to edges
edges = nxGraph.edges(keys = True)
attributeDict = dict()
for edge in edges:
attributeDict[edge] = edge[2].__str__()
networkx.set_edge_attributes(nxGraph, attributeDict, ID_NAME)
# add label to nodes
nodes = nxGraph.nodes
attributeDict = dict()
for node in nodes:
attributeDict[node] = node.__str__()
networkx.set_node_attributes(nxGraph, attributeDict, ID_NAME)
[docs]def addDescriptionAttribute(nxGraph: networkx.classes.MultiGraph):
"""
Adds the "custom_description" attribute to each node and edge.
Add an attribute to nodes and edges called "custom_description" (see module variable :attr:`DESCRIPTION_NAME`) containing the node's or edge's `.description` field, if there is any.
Parameters
----------
nxGraph : networkx.classes.MultiGraph
A NetworkX graph object.
"""
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError()
# add description to edges
edges = nxGraph.edges(keys = True)
attributeDict = dict()
for edge in edges:
try:
attributeDict[edge] = edge[2].description if edge[2].description is not None else ''
except AttributeError:
continue
networkx.set_edge_attributes(nxGraph, attributeDict, DESCRIPTION_NAME)
# add description to nodes
nodes = nxGraph.nodes
attributeDict = dict()
for node in nodes:
try:
attributeDict[node] = node.description if node.description is not None else ''
except AttributeError:
continue
networkx.set_node_attributes(nxGraph, attributeDict, DESCRIPTION_NAME)
[docs]def addReactionAttribute(nxGraph: networkx.classes.MultiGraph):
"""
Adds the "custom_reaction" attribute to each edge.
Add an attribute to edges called "custom_reaction" (see module variable :attr:`REACTION_NAME`) containing the edge's `.reaction` field, if there is any.
Parameters
----------
nxGraph : networkx.classes.MultiGraph
A NetworkX graph object.
"""
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError()
# add reaction to edges
edges = nxGraph.edges(keys = True)
attributeDict = dict()
for edge in edges:
try:
attributeDict[edge] = edge[2].reaction if edge[2].reaction is not None else ''
except AttributeError:
continue
networkx.set_edge_attributes(nxGraph, attributeDict, REACTION_NAME)
[docs]def addMajorityAttribute(graph: Models.CommonGraphApi, totalNumberOfOrganisms: int):
"""
Adds the "custom_majority" attribute to each node and edge.
Add an attribute to nodes and edges called "custom_majority" (see module variable :attr:`MAJORITY_NAME`).
It contains each node's or edge's majority percentage, if the graph contains counts (`graph.nodeCounts` or `graph.edgeCounts`, see :class:`FEV_KEGG.Graph.Models.CommonGraphApi`).
For example, say edge A were to occur in 52 of all organisms used to create the `graph`, then ```graph.edgeCounts[A] == 52```.
Now we have also ```totalNumberOfOrganisms == 76```. Then, we will write ```ceil(52/76*100) = 68``` into the "custom_majority" attribute.
Parameters
----------
graph : Models.CommonGraphApi
A graph object.
totalNumberOfOrganisms : int
Total number of organisms which were involved in creating the `graph`. This is used to calculate the percentage of counts. 100% == `totalNumberOfOrganisms`.
"""
nxGraph = graph.underlyingRawGraph
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError("This graph model can not be annotated for majority, yet.")
# add majority to edges
if graph.edgeCounts is not None:
attributeDict = dict()
for edge in graph.getEdges():
attributeDict[edge] = math.ceil(graph.edgeCounts.get(edge, 0) / totalNumberOfOrganisms * 100)
networkx.set_edge_attributes(nxGraph, attributeDict, MAJORITY_NAME)
# add majority to nodes
if graph.nodeCounts is not None:
attributeDict = dict()
for node in graph.getNodes():
attributeDict[node] = math.ceil(graph.nodeCounts.get(node, 0) / totalNumberOfOrganisms * 100)
networkx.set_node_attributes(nxGraph, attributeDict, MAJORITY_NAME)
[docs]def addUrlAttribute(nxGraph: networkx.classes.MultiGraph):
"""
Adds the "URL" attribute to each edge.
Add an attribute to edges called "URL" (see module variable :attr:`URL_NAME`) containing the edge's or node's URL to KEGG.
Parameters
----------
nxGraph : networkx.classes.MultiGraph
A NetworkX graph object.
"""
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError()
# add label to edges
edges = nxGraph.edges(keys = True)
attributeDict = dict()
for edge in edges:
attributeDict[edge] = edge[2].getUrl() if edge[2].getUrl() is not None else ''
networkx.set_edge_attributes(nxGraph, attributeDict, URL_NAME)
# add label to nodes
nodes = nxGraph.nodes
attributeDict = dict()
for node in nodes:
attributeDict[node] = node.getUrl() if node.getUrl() is not None else ''
networkx.set_node_attributes(nxGraph, attributeDict, URL_NAME)
[docs]class Colour(Enum):
BLUE = '#4444FF'
RED = '#FF5555'
PINK = '#FF66FF'
GREEN = '#55FF55'
YELLOW = '#FFFF55'
TURQUOISE = '#55FFFF'
[docs]def addColourAttribute(graph: Models.CommonGraphApi, colour : Colour, nodes = False, edges = False):
"""
Adds the "colour" attribute to selected nodes/edges.
If both, `nodes` and `edges` are *False*, nothing is coloured.
Adds an attribute to nodes and edges called "colour" (see module variable :attr:`COLOUR_NAME`) containing the string of a hex value of a colour in RGB, see :class:`Colour`.
Parameters
----------
graph : Models.CommonGraphApi
A graph object.
colour : Colour
Colour to use.
nodes : Iterable[NodeView] or bool, optional
Nodes to be coloured, i.e. annotated with the "colour" attribute containing `colour`. If *True*, all nodes are coloured.
edges : Iterable[EdgeView] or bool, optional
Edges to be coloured, i.e. annotated with the "colour" attribute containing `colour`. If *True*, all edges are coloured.
Warnings
----
Any operation on the resulting graph, e.g. :func:`FEV_KEGG.Graph.Models.CommonGraphApi.intersection`, removes the colouring. It actually removes all attributes.
"""
nxGraph = graph.underlyingRawGraph
if not isinstance(nxGraph, networkx.classes.graph.Graph):
raise NotImplementedError("This graph model can not be coloured, yet.")
# add color to edges
if edges is not False:
# colour something
if edges is True:
# colour everything
edgesToColour = nxGraph.edges
else:
# colour what is in `edges`
edgesToColour = edges
attributeDict = dict()
for edge in edgesToColour:
attributeDict[edge] = colour.value
networkx.set_edge_attributes(nxGraph, attributeDict, COLOUR_NAME)
# add color to nodes
if nodes is not False:
# colour something
if nodes is True:
# colour everything
nodesToColour = nxGraph.nodes
else:
# colour what is in `nodes`
nodesToColour = nodes
attributeDict = dict()
for node in nodesToColour:
attributeDict[node] = colour.value
networkx.set_node_attributes(nxGraph, attributeDict, COLOUR_NAME)
[docs]def toGraphML(graph: Models.CommonGraphApi, file, inCacheFolder = False):
"""
Export `graph` to `file` in GraphML format.
Each of the graph element's attributes is translated into a column.
Parameters
----------
graph : Models.CommonGraphApi
The graph to be exported.
file : str
Path and name of the exported file. See `inCacheFolder`.
inCacheFolder : bool, optional
If *True*, interpret `file` relative to the cache folder. See :attr:`FEV_KEGG.settings.cachePath`.
If *False*, interpret `file` relative to the current working directory.
Raises
------
NotImplementedError
If `graph` is not of a NetworkX type.
"""
nxGraph = graph.underlyingRawGraph
if isinstance(nxGraph, networkx.classes.graph.Graph):
if inCacheFolder is True:
file = os.path.join(settings.cachePath, file)
dirName = os.path.dirname(file)
if not os.path.isdir(dirName) and dirName != '':
os.makedirs(os.path.dirname(file))
networkx.write_graphml(nxGraph, file + '.graphml', prettyprint=False)
else:
raise NotImplementedError()
[docs]def toGML(graph: Models.CommonGraphApi, file, inCacheFolder = False):
"""
Export `graph` to `file` in GML format.
Each of the graph element's attributes is translated into a column.
Parameters
----------
graph : Models.CommonGraphApi
The graph to be exported.
file : str
Path and name of the exported file. See `inCacheFolder`.
inCacheFolder : bool, optional
If *True*, interpret `file` relative to the cache folder. See :attr:`FEV_KEGG.settings.cachePath`.
If *False*, interpret `file` relative to the current working directory.
Raises
------
NotImplementedError
If `graph` is not of a NetworkX type.
"""
nxGraph = graph.underlyingRawGraph
if isinstance(nxGraph, networkx.classes.graph.Graph):
if inCacheFolder is True:
file = os.path.join(settings.cachePath, file)
dirName = os.path.dirname(file)
if not os.path.isdir(dirName) and dirName != '':
os.makedirs(os.path.dirname(file))
networkx.write_gml(nxGraph, file + '.gml', lambda x: x.__str__())
else:
raise NotImplementedError()
[docs]def forCytoscape(graph: Models.CommonGraphApi, file, inCacheFolder = False, addDescriptions = True, totalNumberOfOrganisms: int = None):
"""
Export `graph` to `file` in GraphML format, including some tweaks for Cytoscape.
Parameters
----------
graph : Models.CommonGraphApi
The graph to be exported.
file : str
Path and name of the exported file. See `inCacheFolder`.
inCacheFolder : bool, optional
If *True*, interpret `file` relative to the cache folder. See :attr:`FEV_KEGG.settings.cachePath`.
If *False*, interpret `file` relative to the current working directory.
addDescriptions : bool, optional
If *True*, downloads additional descriptions for substance
totalNumberOfOrganisms : int, optional
Total number of organisms which were involved in creating the `graph`. This is used to calculate the percentage of counts. 100% == `totalNumberOfOrganisms`.
If *None*, no majority attribute is added. Only relevant if `graph` has counts, which it usually does not.
See :func:`addMajorityAttribute` for more info.
Raises
------
NotImplementedError
If `graph` is not of a NetworkX type.
Warnings
--------
Adding extra descriptions, when `addDescriptions` == *True* (default!) is a lengthy process and can take some minutes.
"""
# fetch extra descriptions, so they can be saved in attributes
if addDescriptions is True:
graph.addSubstanceDescriptions()
if isinstance(graph, SubstanceEcGraph):
graph.addEcDescriptions()
# add certain attributes to the graph, so they can be stored in the resulting XML
addLabelAttribute(graph.underlyingRawGraph)
addIdAttribute(graph.underlyingRawGraph)
addDescriptionAttribute(graph.underlyingRawGraph)
addUrlAttribute(graph.underlyingRawGraph)
if addDescriptions is True and isinstance(graph, SubstanceEcGraph):
addReactionAttribute(graph.underlyingRawGraph)
if totalNumberOfOrganisms is not None:
addMajorityAttribute(graph, totalNumberOfOrganisms)
toGraphML(graph, file, inCacheFolder=inCacheFolder)