| """ |
| ***** |
| Pydot |
| ***** |
| |
| Import and export NetworkX graphs in Graphviz dot format using pydot. |
| |
| Either this module or nx_agraph can be used to interface with graphviz. |
| |
| Examples |
| -------- |
| >>> G = nx.complete_graph(5) |
| >>> PG = nx.nx_pydot.to_pydot(G) |
| >>> H = nx.nx_pydot.from_pydot(PG) |
| |
| See Also |
| -------- |
| - pydot: https://github.com/erocarrera/pydot |
| - Graphviz: https://www.graphviz.org |
| - DOT Language: http://www.graphviz.org/doc/info/lang.html |
| """ |
| import warnings |
| from locale import getpreferredencoding |
|
|
| import networkx as nx |
| from networkx.utils import open_file |
|
|
| __all__ = [ |
| "write_dot", |
| "read_dot", |
| "graphviz_layout", |
| "pydot_layout", |
| "to_pydot", |
| "from_pydot", |
| ] |
|
|
|
|
| @open_file(1, mode="w") |
| def write_dot(G, path): |
| """Write NetworkX graph G to Graphviz dot format on path. |
| |
| Path can be a string or a file handle. |
| """ |
| msg = ( |
| "nx.nx_pydot.write_dot depends on the pydot package, which has" |
| "known issues and is not actively maintained. Consider using" |
| "nx.nx_agraph.write_dot instead.\n\n" |
| "See https://github.com/networkx/networkx/issues/5723" |
| ) |
| warnings.warn(msg, DeprecationWarning, stacklevel=2) |
| P = to_pydot(G) |
| path.write(P.to_string()) |
| return |
|
|
|
|
| @open_file(0, mode="r") |
| def read_dot(path): |
| """Returns a NetworkX :class:`MultiGraph` or :class:`MultiDiGraph` from the |
| dot file with the passed path. |
| |
| If this file contains multiple graphs, only the first such graph is |
| returned. All graphs _except_ the first are silently ignored. |
| |
| Parameters |
| ---------- |
| path : str or file |
| Filename or file handle. |
| |
| Returns |
| ------- |
| G : MultiGraph or MultiDiGraph |
| A :class:`MultiGraph` or :class:`MultiDiGraph`. |
| |
| Notes |
| ----- |
| Use `G = nx.Graph(nx.nx_pydot.read_dot(path))` to return a :class:`Graph` instead of a |
| :class:`MultiGraph`. |
| """ |
| import pydot |
|
|
| msg = ( |
| "nx.nx_pydot.read_dot depends on the pydot package, which has" |
| "known issues and is not actively maintained. Consider using" |
| "nx.nx_agraph.read_dot instead.\n\n" |
| "See https://github.com/networkx/networkx/issues/5723" |
| ) |
| warnings.warn(msg, DeprecationWarning, stacklevel=2) |
|
|
| data = path.read() |
|
|
| |
| P_list = pydot.graph_from_dot_data(data) |
|
|
| |
| return from_pydot(P_list[0]) |
|
|
|
|
| def from_pydot(P): |
| """Returns a NetworkX graph from a Pydot graph. |
| |
| Parameters |
| ---------- |
| P : Pydot graph |
| A graph created with Pydot |
| |
| Returns |
| ------- |
| G : NetworkX multigraph |
| A MultiGraph or MultiDiGraph. |
| |
| Examples |
| -------- |
| >>> K5 = nx.complete_graph(5) |
| >>> A = nx.nx_pydot.to_pydot(K5) |
| >>> G = nx.nx_pydot.from_pydot(A) # return MultiGraph |
| |
| # make a Graph instead of MultiGraph |
| >>> G = nx.Graph(nx.nx_pydot.from_pydot(A)) |
| |
| """ |
| msg = ( |
| "nx.nx_pydot.from_pydot depends on the pydot package, which has" |
| "known issues and is not actively maintained.\n\n" |
| "See https://github.com/networkx/networkx/issues/5723" |
| ) |
| warnings.warn(msg, DeprecationWarning, stacklevel=2) |
|
|
| if P.get_strict(None): |
| multiedges = False |
| else: |
| multiedges = True |
|
|
| if P.get_type() == "graph": |
| if multiedges: |
| N = nx.MultiGraph() |
| else: |
| N = nx.Graph() |
| else: |
| if multiedges: |
| N = nx.MultiDiGraph() |
| else: |
| N = nx.DiGraph() |
|
|
| |
| name = P.get_name().strip('"') |
| if name != "": |
| N.name = name |
|
|
| |
| for p in P.get_node_list(): |
| n = p.get_name().strip('"') |
| if n in ("node", "graph", "edge"): |
| continue |
| N.add_node(n, **p.get_attributes()) |
|
|
| |
| for e in P.get_edge_list(): |
| u = e.get_source() |
| v = e.get_destination() |
| attr = e.get_attributes() |
| s = [] |
| d = [] |
|
|
| if isinstance(u, str): |
| s.append(u.strip('"')) |
| else: |
| for unodes in u["nodes"]: |
| s.append(unodes.strip('"')) |
|
|
| if isinstance(v, str): |
| d.append(v.strip('"')) |
| else: |
| for vnodes in v["nodes"]: |
| d.append(vnodes.strip('"')) |
|
|
| for source_node in s: |
| for destination_node in d: |
| N.add_edge(source_node, destination_node, **attr) |
|
|
| |
| pattr = P.get_attributes() |
| if pattr: |
| N.graph["graph"] = pattr |
| try: |
| N.graph["node"] = P.get_node_defaults()[0] |
| except (IndexError, TypeError): |
| pass |
| try: |
| N.graph["edge"] = P.get_edge_defaults()[0] |
| except (IndexError, TypeError): |
| pass |
| return N |
|
|
|
|
| def _check_colon_quotes(s): |
| |
| |
| |
| return ":" in s and (s[0] != '"' or s[-1] != '"') |
|
|
|
|
| def to_pydot(N): |
| """Returns a pydot graph from a NetworkX graph N. |
| |
| Parameters |
| ---------- |
| N : NetworkX graph |
| A graph created with NetworkX |
| |
| Examples |
| -------- |
| >>> K5 = nx.complete_graph(5) |
| >>> P = nx.nx_pydot.to_pydot(K5) |
| |
| Notes |
| ----- |
| |
| """ |
| import pydot |
|
|
| msg = ( |
| "nx.nx_pydot.to_pydot depends on the pydot package, which has" |
| "known issues and is not actively maintained.\n\n" |
| "See https://github.com/networkx/networkx/issues/5723" |
| ) |
| warnings.warn(msg, DeprecationWarning, stacklevel=2) |
|
|
| |
| if N.is_directed(): |
| graph_type = "digraph" |
| else: |
| graph_type = "graph" |
| strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph() |
|
|
| name = N.name |
| graph_defaults = N.graph.get("graph", {}) |
| if name == "": |
| P = pydot.Dot("", graph_type=graph_type, strict=strict, **graph_defaults) |
| else: |
| P = pydot.Dot( |
| f'"{name}"', graph_type=graph_type, strict=strict, **graph_defaults |
| ) |
| try: |
| P.set_node_defaults(**N.graph["node"]) |
| except KeyError: |
| pass |
| try: |
| P.set_edge_defaults(**N.graph["edge"]) |
| except KeyError: |
| pass |
|
|
| for n, nodedata in N.nodes(data=True): |
| str_nodedata = {str(k): str(v) for k, v in nodedata.items()} |
| |
| n = str(n) |
| raise_error = _check_colon_quotes(n) or ( |
| any( |
| (_check_colon_quotes(k) or _check_colon_quotes(v)) |
| for k, v in str_nodedata.items() |
| ) |
| ) |
| if raise_error: |
| raise ValueError( |
| f'Node names and attributes should not contain ":" unless they are quoted with "".\ |
| For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\ |
| Please refer https://github.com/pydot/pydot/issues/258' |
| ) |
| p = pydot.Node(n, **str_nodedata) |
| P.add_node(p) |
|
|
| if N.is_multigraph(): |
| for u, v, key, edgedata in N.edges(data=True, keys=True): |
| str_edgedata = {str(k): str(v) for k, v in edgedata.items() if k != "key"} |
| u, v = str(u), str(v) |
| raise_error = ( |
| _check_colon_quotes(u) |
| or _check_colon_quotes(v) |
| or ( |
| any( |
| (_check_colon_quotes(k) or _check_colon_quotes(val)) |
| for k, val in str_edgedata.items() |
| ) |
| ) |
| ) |
| if raise_error: |
| raise ValueError( |
| f'Node names and attributes should not contain ":" unless they are quoted with "".\ |
| For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\ |
| Please refer https://github.com/pydot/pydot/issues/258' |
| ) |
| edge = pydot.Edge(u, v, key=str(key), **str_edgedata) |
| P.add_edge(edge) |
|
|
| else: |
| for u, v, edgedata in N.edges(data=True): |
| str_edgedata = {str(k): str(v) for k, v in edgedata.items()} |
| u, v = str(u), str(v) |
| raise_error = ( |
| _check_colon_quotes(u) |
| or _check_colon_quotes(v) |
| or ( |
| any( |
| (_check_colon_quotes(k) or _check_colon_quotes(val)) |
| for k, val in str_edgedata.items() |
| ) |
| ) |
| ) |
| if raise_error: |
| raise ValueError( |
| f'Node names and attributes should not contain ":" unless they are quoted with "".\ |
| For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\ |
| Please refer https://github.com/pydot/pydot/issues/258' |
| ) |
| edge = pydot.Edge(u, v, **str_edgedata) |
| P.add_edge(edge) |
| return P |
|
|
|
|
| def graphviz_layout(G, prog="neato", root=None): |
| """Create node positions using Pydot and Graphviz. |
| |
| Returns a dictionary of positions keyed by node. |
| |
| Parameters |
| ---------- |
| G : NetworkX Graph |
| The graph for which the layout is computed. |
| prog : string (default: 'neato') |
| The name of the GraphViz program to use for layout. |
| Options depend on GraphViz version but may include: |
| 'dot', 'twopi', 'fdp', 'sfdp', 'circo' |
| root : Node from G or None (default: None) |
| The node of G from which to start some layout algorithms. |
| |
| Returns |
| ------- |
| Dictionary of (x, y) positions keyed by node. |
| |
| Examples |
| -------- |
| >>> G = nx.complete_graph(4) |
| >>> pos = nx.nx_pydot.graphviz_layout(G) |
| >>> pos = nx.nx_pydot.graphviz_layout(G, prog="dot") |
| |
| Notes |
| ----- |
| This is a wrapper for pydot_layout. |
| """ |
| msg = ( |
| "nx.nx_pydot.graphviz_layout depends on the pydot package, which has" |
| "known issues and is not actively maintained. Consider using" |
| "nx.nx_agraph.graphviz_layout instead.\n\n" |
| "See https://github.com/networkx/networkx/issues/5723" |
| ) |
| warnings.warn(msg, DeprecationWarning, stacklevel=2) |
|
|
| return pydot_layout(G=G, prog=prog, root=root) |
|
|
|
|
| def pydot_layout(G, prog="neato", root=None): |
| """Create node positions using :mod:`pydot` and Graphviz. |
| |
| Parameters |
| ---------- |
| G : Graph |
| NetworkX graph to be laid out. |
| prog : string (default: 'neato') |
| Name of the GraphViz command to use for layout. |
| Options depend on GraphViz version but may include: |
| 'dot', 'twopi', 'fdp', 'sfdp', 'circo' |
| root : Node from G or None (default: None) |
| The node of G from which to start some layout algorithms. |
| |
| Returns |
| ------- |
| dict |
| Dictionary of positions keyed by node. |
| |
| Examples |
| -------- |
| >>> G = nx.complete_graph(4) |
| >>> pos = nx.nx_pydot.pydot_layout(G) |
| >>> pos = nx.nx_pydot.pydot_layout(G, prog="dot") |
| |
| Notes |
| ----- |
| If you use complex node objects, they may have the same string |
| representation and GraphViz could treat them as the same node. |
| The layout may assign both nodes a single location. See Issue #1568 |
| If this occurs in your case, consider relabeling the nodes just |
| for the layout computation using something similar to:: |
| |
| H = nx.convert_node_labels_to_integers(G, label_attribute='node_label') |
| H_layout = nx.nx_pydot.pydot_layout(G, prog='dot') |
| G_layout = {H.nodes[n]['node_label']: p for n, p in H_layout.items()} |
| |
| """ |
| import pydot |
|
|
| msg = ( |
| "nx.nx_pydot.pydot_layout depends on the pydot package, which has" |
| "known issues and is not actively maintained.\n\n" |
| "See https://github.com/networkx/networkx/issues/5723" |
| ) |
| warnings.warn(msg, DeprecationWarning, stacklevel=2) |
| P = to_pydot(G) |
| if root is not None: |
| P.set("root", str(root)) |
|
|
| |
| |
| D_bytes = P.create_dot(prog=prog) |
|
|
| |
| D = str(D_bytes, encoding=getpreferredencoding()) |
|
|
| if D == "": |
| print(f"Graphviz layout with {prog} failed") |
| print() |
| print("To debug what happened try:") |
| print("P = nx.nx_pydot.to_pydot(G)") |
| print('P.write_dot("file.dot")') |
| print(f"And then run {prog} on file.dot") |
| return |
|
|
| |
| Q_list = pydot.graph_from_dot_data(D) |
| assert len(Q_list) == 1 |
|
|
| |
| Q = Q_list[0] |
|
|
| node_pos = {} |
| for n in G.nodes(): |
| str_n = str(n) |
| |
| if _check_colon_quotes(str_n): |
| raise ValueError( |
| f'Node names and node attributes should not contain ":" unless they are quoted with "".\ |
| For example the string \'attribute:data1\' should be written as \'"attribute:data1"\'.\ |
| Please refer https://github.com/pydot/pydot/issues/258' |
| ) |
| pydot_node = pydot.Node(str_n).get_name() |
| node = Q.get_node(pydot_node) |
|
|
| if isinstance(node, list): |
| node = node[0] |
| pos = node.get_pos()[1:-1] |
| if pos is not None: |
| xx, yy = pos.split(",") |
| node_pos[n] = (float(xx), float(yy)) |
| return node_pos |
|
|