using for loop to install conda package
This commit is contained in:
7
.CondaPkg/env/Lib/site-packages/networkx/drawing/__init__.py
vendored
Normal file
7
.CondaPkg/env/Lib/site-packages/networkx/drawing/__init__.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# graph drawing and interface to graphviz
|
||||
|
||||
from .layout import *
|
||||
from .nx_latex import *
|
||||
from .nx_pylab import *
|
||||
from . import nx_agraph
|
||||
from . import nx_pydot
|
||||
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/layout.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/layout.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_agraph.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_agraph.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_latex.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_latex.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_pydot.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_pydot.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_pylab.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/__pycache__/nx_pylab.cpython-311.pyc
vendored
Normal file
Binary file not shown.
1302
.CondaPkg/env/Lib/site-packages/networkx/drawing/layout.py
vendored
Normal file
1302
.CondaPkg/env/Lib/site-packages/networkx/drawing/layout.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
460
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_agraph.py
vendored
Normal file
460
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_agraph.py
vendored
Normal file
@@ -0,0 +1,460 @@
|
||||
"""
|
||||
***************
|
||||
Graphviz AGraph
|
||||
***************
|
||||
|
||||
Interface to pygraphviz AGraph class.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.complete_graph(5)
|
||||
>>> A = nx.nx_agraph.to_agraph(G)
|
||||
>>> H = nx.nx_agraph.from_agraph(A)
|
||||
|
||||
See Also
|
||||
--------
|
||||
- Pygraphviz: http://pygraphviz.github.io/
|
||||
- Graphviz: https://www.graphviz.org
|
||||
- DOT Language: http://www.graphviz.org/doc/info/lang.html
|
||||
"""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = [
|
||||
"from_agraph",
|
||||
"to_agraph",
|
||||
"write_dot",
|
||||
"read_dot",
|
||||
"graphviz_layout",
|
||||
"pygraphviz_layout",
|
||||
"view_pygraphviz",
|
||||
]
|
||||
|
||||
|
||||
def from_agraph(A, create_using=None):
|
||||
"""Returns a NetworkX Graph or DiGraph from a PyGraphviz graph.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
A : PyGraphviz AGraph
|
||||
A graph created with PyGraphviz
|
||||
|
||||
create_using : NetworkX graph constructor, optional (default=None)
|
||||
Graph type to create. If graph instance, then cleared before populated.
|
||||
If `None`, then the appropriate Graph type is inferred from `A`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> K5 = nx.complete_graph(5)
|
||||
>>> A = nx.nx_agraph.to_agraph(K5)
|
||||
>>> G = nx.nx_agraph.from_agraph(A)
|
||||
|
||||
Notes
|
||||
-----
|
||||
The Graph G will have a dictionary G.graph_attr containing
|
||||
the default graphviz attributes for graphs, nodes and edges.
|
||||
|
||||
Default node attributes will be in the dictionary G.node_attr
|
||||
which is keyed by node.
|
||||
|
||||
Edge attributes will be returned as edge data in G. With
|
||||
edge_attr=False the edge data will be the Graphviz edge weight
|
||||
attribute or the value 1 if no edge weight attribute is found.
|
||||
|
||||
"""
|
||||
if create_using is None:
|
||||
if A.is_directed():
|
||||
if A.is_strict():
|
||||
create_using = nx.DiGraph
|
||||
else:
|
||||
create_using = nx.MultiDiGraph
|
||||
else:
|
||||
if A.is_strict():
|
||||
create_using = nx.Graph
|
||||
else:
|
||||
create_using = nx.MultiGraph
|
||||
|
||||
# assign defaults
|
||||
N = nx.empty_graph(0, create_using)
|
||||
if A.name is not None:
|
||||
N.name = A.name
|
||||
|
||||
# add graph attributes
|
||||
N.graph.update(A.graph_attr)
|
||||
|
||||
# add nodes, attributes to N.node_attr
|
||||
for n in A.nodes():
|
||||
str_attr = {str(k): v for k, v in n.attr.items()}
|
||||
N.add_node(str(n), **str_attr)
|
||||
|
||||
# add edges, assign edge data as dictionary of attributes
|
||||
for e in A.edges():
|
||||
u, v = str(e[0]), str(e[1])
|
||||
attr = dict(e.attr)
|
||||
str_attr = {str(k): v for k, v in attr.items()}
|
||||
if not N.is_multigraph():
|
||||
if e.name is not None:
|
||||
str_attr["key"] = e.name
|
||||
N.add_edge(u, v, **str_attr)
|
||||
else:
|
||||
N.add_edge(u, v, key=e.name, **str_attr)
|
||||
|
||||
# add default attributes for graph, nodes, and edges
|
||||
# hang them on N.graph_attr
|
||||
N.graph["graph"] = dict(A.graph_attr)
|
||||
N.graph["node"] = dict(A.node_attr)
|
||||
N.graph["edge"] = dict(A.edge_attr)
|
||||
return N
|
||||
|
||||
|
||||
def to_agraph(N):
|
||||
"""Returns a pygraphviz graph from a NetworkX graph N.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
N : NetworkX graph
|
||||
A graph created with NetworkX
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> K5 = nx.complete_graph(5)
|
||||
>>> A = nx.nx_agraph.to_agraph(K5)
|
||||
|
||||
Notes
|
||||
-----
|
||||
If N has an dict N.graph_attr an attempt will be made first
|
||||
to copy properties attached to the graph (see from_agraph)
|
||||
and then updated with the calling arguments if any.
|
||||
|
||||
"""
|
||||
try:
|
||||
import pygraphviz
|
||||
except ImportError as err:
|
||||
raise ImportError(
|
||||
"requires pygraphviz " "http://pygraphviz.github.io/"
|
||||
) from err
|
||||
directed = N.is_directed()
|
||||
strict = nx.number_of_selfloops(N) == 0 and not N.is_multigraph()
|
||||
A = pygraphviz.AGraph(name=N.name, strict=strict, directed=directed)
|
||||
|
||||
# default graph attributes
|
||||
A.graph_attr.update(N.graph.get("graph", {}))
|
||||
A.node_attr.update(N.graph.get("node", {}))
|
||||
A.edge_attr.update(N.graph.get("edge", {}))
|
||||
|
||||
A.graph_attr.update(
|
||||
(k, v) for k, v in N.graph.items() if k not in ("graph", "node", "edge")
|
||||
)
|
||||
|
||||
# add nodes
|
||||
for n, nodedata in N.nodes(data=True):
|
||||
A.add_node(n)
|
||||
# Add node data
|
||||
a = A.get_node(n)
|
||||
a.attr.update({k: str(v) for k, v in nodedata.items()})
|
||||
|
||||
# loop over edges
|
||||
if N.is_multigraph():
|
||||
for u, v, key, edgedata in N.edges(data=True, keys=True):
|
||||
str_edgedata = {k: str(v) for k, v in edgedata.items() if k != "key"}
|
||||
A.add_edge(u, v, key=str(key))
|
||||
# Add edge data
|
||||
a = A.get_edge(u, v)
|
||||
a.attr.update(str_edgedata)
|
||||
|
||||
else:
|
||||
for u, v, edgedata in N.edges(data=True):
|
||||
str_edgedata = {k: str(v) for k, v in edgedata.items()}
|
||||
A.add_edge(u, v)
|
||||
# Add edge data
|
||||
a = A.get_edge(u, v)
|
||||
a.attr.update(str_edgedata)
|
||||
|
||||
return A
|
||||
|
||||
|
||||
def write_dot(G, path):
|
||||
"""Write NetworkX graph G to Graphviz dot format on path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : graph
|
||||
A networkx graph
|
||||
path : filename
|
||||
Filename or file handle to write
|
||||
|
||||
Notes
|
||||
-----
|
||||
To use a specific graph layout, call ``A.layout`` prior to `write_dot`.
|
||||
Note that some graphviz layouts are not guaranteed to be deterministic,
|
||||
see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
|
||||
"""
|
||||
A = to_agraph(G)
|
||||
A.write(path)
|
||||
A.clear()
|
||||
return
|
||||
|
||||
|
||||
def read_dot(path):
|
||||
"""Returns a NetworkX graph from a dot file on path.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : file or string
|
||||
File name or file handle to read.
|
||||
"""
|
||||
try:
|
||||
import pygraphviz
|
||||
except ImportError as err:
|
||||
raise ImportError(
|
||||
"read_dot() requires pygraphviz " "http://pygraphviz.github.io/"
|
||||
) from err
|
||||
A = pygraphviz.AGraph(file=path)
|
||||
gr = from_agraph(A)
|
||||
A.clear()
|
||||
return gr
|
||||
|
||||
|
||||
def graphviz_layout(G, prog="neato", root=None, args=""):
|
||||
"""Create node positions for G using Graphviz.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
A graph created with NetworkX
|
||||
prog : string
|
||||
Name of Graphviz layout program
|
||||
root : string, optional
|
||||
Root node for twopi layout
|
||||
args : string, optional
|
||||
Extra arguments to Graphviz layout program
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dictionary of x, y, positions keyed by node.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.petersen_graph()
|
||||
>>> pos = nx.nx_agraph.graphviz_layout(G)
|
||||
>>> pos = nx.nx_agraph.graphviz_layout(G, prog="dot")
|
||||
|
||||
Notes
|
||||
-----
|
||||
This is a wrapper for pygraphviz_layout.
|
||||
|
||||
Note that some graphviz layouts are not guaranteed to be deterministic,
|
||||
see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
|
||||
"""
|
||||
return pygraphviz_layout(G, prog=prog, root=root, args=args)
|
||||
|
||||
|
||||
def pygraphviz_layout(G, prog="neato", root=None, args=""):
|
||||
"""Create node positions for G using Graphviz.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
A graph created with NetworkX
|
||||
prog : string
|
||||
Name of Graphviz layout program
|
||||
root : string, optional
|
||||
Root node for twopi layout
|
||||
args : string, optional
|
||||
Extra arguments to Graphviz layout program
|
||||
|
||||
Returns
|
||||
-------
|
||||
node_pos : dict
|
||||
Dictionary of x, y, positions keyed by node.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.petersen_graph()
|
||||
>>> pos = nx.nx_agraph.graphviz_layout(G)
|
||||
>>> pos = nx.nx_agraph.graphviz_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_agraph.pygraphviz_layout(G, prog="dot")
|
||||
>>> G_layout = {H.nodes[n]["node_label"]: p for n, p in H_layout.items()}
|
||||
|
||||
Note that some graphviz layouts are not guaranteed to be deterministic,
|
||||
see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
|
||||
"""
|
||||
try:
|
||||
import pygraphviz
|
||||
except ImportError as err:
|
||||
raise ImportError(
|
||||
"requires pygraphviz " "http://pygraphviz.github.io/"
|
||||
) from err
|
||||
if root is not None:
|
||||
args += f"-Groot={root}"
|
||||
A = to_agraph(G)
|
||||
A.layout(prog=prog, args=args)
|
||||
node_pos = {}
|
||||
for n in G:
|
||||
node = pygraphviz.Node(A, n)
|
||||
try:
|
||||
xs = node.attr["pos"].split(",")
|
||||
node_pos[n] = tuple(float(x) for x in xs)
|
||||
except:
|
||||
print("no position for node", n)
|
||||
node_pos[n] = (0.0, 0.0)
|
||||
return node_pos
|
||||
|
||||
|
||||
@nx.utils.open_file(5, "w+b")
|
||||
def view_pygraphviz(
|
||||
G, edgelabel=None, prog="dot", args="", suffix="", path=None, show=True
|
||||
):
|
||||
"""Views the graph G using the specified layout algorithm.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The machine to draw.
|
||||
edgelabel : str, callable, None
|
||||
If a string, then it specifies the edge attribute to be displayed
|
||||
on the edge labels. If a callable, then it is called for each
|
||||
edge and it should return the string to be displayed on the edges.
|
||||
The function signature of `edgelabel` should be edgelabel(data),
|
||||
where `data` is the edge attribute dictionary.
|
||||
prog : string
|
||||
Name of Graphviz layout program.
|
||||
args : str
|
||||
Additional arguments to pass to the Graphviz layout program.
|
||||
suffix : str
|
||||
If `filename` is None, we save to a temporary file. The value of
|
||||
`suffix` will appear at the tail end of the temporary filename.
|
||||
path : str, None
|
||||
The filename used to save the image. If None, save to a temporary
|
||||
file. File formats are the same as those from pygraphviz.agraph.draw.
|
||||
show : bool, default = True
|
||||
Whether to display the graph with :mod:`PIL.Image.show`,
|
||||
default is `True`. If `False`, the rendered graph is still available
|
||||
at `path`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
path : str
|
||||
The filename of the generated image.
|
||||
A : PyGraphviz graph
|
||||
The PyGraphviz graph instance used to generate the image.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If this function is called in succession too quickly, sometimes the
|
||||
image is not displayed. So you might consider time.sleep(.5) between
|
||||
calls if you experience problems.
|
||||
|
||||
Note that some graphviz layouts are not guaranteed to be deterministic,
|
||||
see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
|
||||
|
||||
"""
|
||||
if not len(G):
|
||||
raise nx.NetworkXException("An empty graph cannot be drawn.")
|
||||
|
||||
# If we are providing default values for graphviz, these must be set
|
||||
# before any nodes or edges are added to the PyGraphviz graph object.
|
||||
# The reason for this is that default values only affect incoming objects.
|
||||
# If you change the default values after the objects have been added,
|
||||
# then they inherit no value and are set only if explicitly set.
|
||||
|
||||
# to_agraph() uses these values.
|
||||
attrs = ["edge", "node", "graph"]
|
||||
for attr in attrs:
|
||||
if attr not in G.graph:
|
||||
G.graph[attr] = {}
|
||||
|
||||
# These are the default values.
|
||||
edge_attrs = {"fontsize": "10"}
|
||||
node_attrs = {
|
||||
"style": "filled",
|
||||
"fillcolor": "#0000FF40",
|
||||
"height": "0.75",
|
||||
"width": "0.75",
|
||||
"shape": "circle",
|
||||
}
|
||||
graph_attrs = {}
|
||||
|
||||
def update_attrs(which, attrs):
|
||||
# Update graph attributes. Return list of those which were added.
|
||||
added = []
|
||||
for k, v in attrs.items():
|
||||
if k not in G.graph[which]:
|
||||
G.graph[which][k] = v
|
||||
added.append(k)
|
||||
|
||||
def clean_attrs(which, added):
|
||||
# Remove added attributes
|
||||
for attr in added:
|
||||
del G.graph[which][attr]
|
||||
if not G.graph[which]:
|
||||
del G.graph[which]
|
||||
|
||||
# Update all default values
|
||||
update_attrs("edge", edge_attrs)
|
||||
update_attrs("node", node_attrs)
|
||||
update_attrs("graph", graph_attrs)
|
||||
|
||||
# Convert to agraph, so we inherit default values
|
||||
A = to_agraph(G)
|
||||
|
||||
# Remove the default values we added to the original graph.
|
||||
clean_attrs("edge", edge_attrs)
|
||||
clean_attrs("node", node_attrs)
|
||||
clean_attrs("graph", graph_attrs)
|
||||
|
||||
# If the user passed in an edgelabel, we update the labels for all edges.
|
||||
if edgelabel is not None:
|
||||
if not callable(edgelabel):
|
||||
|
||||
def func(data):
|
||||
return "".join([" ", str(data[edgelabel]), " "])
|
||||
|
||||
else:
|
||||
func = edgelabel
|
||||
|
||||
# update all the edge labels
|
||||
if G.is_multigraph():
|
||||
for u, v, key, data in G.edges(keys=True, data=True):
|
||||
# PyGraphviz doesn't convert the key to a string. See #339
|
||||
edge = A.get_edge(u, v, str(key))
|
||||
edge.attr["label"] = str(func(data))
|
||||
else:
|
||||
for u, v, data in G.edges(data=True):
|
||||
edge = A.get_edge(u, v)
|
||||
edge.attr["label"] = str(func(data))
|
||||
|
||||
if path is None:
|
||||
ext = "png"
|
||||
if suffix:
|
||||
suffix = f"_{suffix}.{ext}"
|
||||
else:
|
||||
suffix = f".{ext}"
|
||||
path = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
|
||||
else:
|
||||
# Assume the decorator worked and it is a file-object.
|
||||
pass
|
||||
|
||||
# Write graph to file
|
||||
A.draw(path=path, format=None, prog=prog, args=args)
|
||||
path.close()
|
||||
|
||||
# Show graph in a new window (depends on platform configuration)
|
||||
if show:
|
||||
from PIL import Image
|
||||
|
||||
Image.open(path.name).show()
|
||||
|
||||
return path.name, A
|
||||
571
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_latex.py
vendored
Normal file
571
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_latex.py
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
r"""
|
||||
*****
|
||||
LaTeX
|
||||
*****
|
||||
|
||||
Export NetworkX graphs in LaTeX format using the TikZ library within TeX/LaTeX.
|
||||
Usually, you will want the drawing to appear in a figure environment so
|
||||
you use ``to_latex(G, caption="A caption")``. If you want the raw
|
||||
drawing commands without a figure environment use :func:`to_latex_raw`.
|
||||
And if you want to write to a file instead of just returning the latex
|
||||
code as a string, use ``write_latex(G, "filename.tex", caption="A caption")``.
|
||||
|
||||
To construct a figure with subfigures for each graph to be shown, provide
|
||||
``to_latex`` or ``write_latex`` a list of graphs, a list of subcaptions,
|
||||
and a number of rows of subfigures inside the figure.
|
||||
|
||||
To be able to refer to the figures or subfigures in latex using ``\\ref``,
|
||||
the keyword ``latex_label`` is available for figures and `sub_labels` for
|
||||
a list of labels, one for each subfigure.
|
||||
|
||||
We intend to eventually provide an interface to the TikZ Graph
|
||||
features which include e.g. layout algorithms.
|
||||
|
||||
Let us know via github what you'd like to see available, or better yet
|
||||
give us some code to do it, or even better make a github pull request
|
||||
to add the feature.
|
||||
|
||||
The TikZ approach
|
||||
=================
|
||||
Drawing options can be stored on the graph as node/edge attributes, or
|
||||
can be provided as dicts keyed by node/edge to a string of the options
|
||||
for that node/edge. Similarly a label can be shown for each node/edge
|
||||
by specifying the labels as graph node/edge attributes or by providing
|
||||
a dict keyed by node/edge to the text to be written for that node/edge.
|
||||
|
||||
Options for the tikzpicture environment (e.g. "[scale=2]") can be provided
|
||||
via a keyword argument. Similarly default node and edge options can be
|
||||
provided through keywords arguments. The default node options are applied
|
||||
to the single TikZ "path" that draws all nodes (and no edges). The default edge
|
||||
options are applied to a TikZ "scope" which contains a path for each edge.
|
||||
|
||||
Examples
|
||||
========
|
||||
>>> G = nx.path_graph(3)
|
||||
>>> nx.write_latex(G, "just_my_figure.tex", as_document=True)
|
||||
>>> nx.write_latex(G, "my_figure.tex", caption="A path graph", latex_label="fig1")
|
||||
>>> latex_code = nx.to_latex(G) # a string rather than a file
|
||||
|
||||
You can change many features of the nodes and edges.
|
||||
|
||||
>>> G = nx.path_graph(4, create_using=nx.DiGraph)
|
||||
>>> pos = {n: (n, n) for n in G} # nodes set on a line
|
||||
|
||||
>>> G.nodes[0]["style"] = "blue"
|
||||
>>> G.nodes[2]["style"] = "line width=3,draw"
|
||||
>>> G.nodes[3]["label"] = "Stop"
|
||||
>>> G.edges[(0, 1)]["label"] = "1st Step"
|
||||
>>> G.edges[(0, 1)]["label_opts"] = "near start"
|
||||
>>> G.edges[(1, 2)]["style"] = "line width=3"
|
||||
>>> G.edges[(1, 2)]["label"] = "2nd Step"
|
||||
>>> G.edges[(2, 3)]["style"] = "green"
|
||||
>>> G.edges[(2, 3)]["label"] = "3rd Step"
|
||||
>>> G.edges[(2, 3)]["label_opts"] = "near end"
|
||||
|
||||
>>> nx.write_latex(G, "latex_graph.tex", pos=pos, as_document=True)
|
||||
|
||||
Then compile the LaTeX using something like ``pdflatex latex_graph.tex``
|
||||
and view the pdf file created: ``latex_graph.pdf``.
|
||||
|
||||
If you want **subfigures** each containing one graph, you can input a list of graphs.
|
||||
|
||||
>>> H1 = nx.path_graph(4)
|
||||
>>> H2 = nx.complete_graph(4)
|
||||
>>> H3 = nx.path_graph(8)
|
||||
>>> H4 = nx.complete_graph(8)
|
||||
>>> graphs = [H1, H2, H3, H4]
|
||||
>>> caps = ["Path 4", "Complete graph 4", "Path 8", "Complete graph 8"]
|
||||
>>> lbls = ["fig2a", "fig2b", "fig2c", "fig2d"]
|
||||
>>> nx.write_latex(graphs, "subfigs.tex", n_rows=2, sub_captions=caps, sub_labels=lbls)
|
||||
>>> latex_code = nx.to_latex(graphs, n_rows=2, sub_captions=caps, sub_labels=lbls)
|
||||
|
||||
>>> node_color = {0: "red", 1: "orange", 2: "blue", 3: "gray!90"}
|
||||
>>> edge_width = {e: "line width=1.5" for e in H3.edges}
|
||||
>>> pos = nx.circular_layout(H3)
|
||||
>>> latex_code = nx.to_latex(H3, pos, node_options=node_color, edge_options=edge_width)
|
||||
>>> print(latex_code)
|
||||
\documentclass{report}
|
||||
\usepackage{tikz}
|
||||
\usepackage{subcaption}
|
||||
<BLANKLINE>
|
||||
\begin{document}
|
||||
\begin{figure}
|
||||
\begin{tikzpicture}
|
||||
\draw
|
||||
(1.0, 0.0) node[red] (0){0}
|
||||
(0.707, 0.707) node[orange] (1){1}
|
||||
(-0.0, 1.0) node[blue] (2){2}
|
||||
(-0.707, 0.707) node[gray!90] (3){3}
|
||||
(-1.0, -0.0) node (4){4}
|
||||
(-0.707, -0.707) node (5){5}
|
||||
(0.0, -1.0) node (6){6}
|
||||
(0.707, -0.707) node (7){7};
|
||||
\begin{scope}[-]
|
||||
\draw[line width=1.5] (0) to (1);
|
||||
\draw[line width=1.5] (1) to (2);
|
||||
\draw[line width=1.5] (2) to (3);
|
||||
\draw[line width=1.5] (3) to (4);
|
||||
\draw[line width=1.5] (4) to (5);
|
||||
\draw[line width=1.5] (5) to (6);
|
||||
\draw[line width=1.5] (6) to (7);
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\end{figure}
|
||||
\end{document}
|
||||
|
||||
Notes
|
||||
-----
|
||||
If you want to change the preamble/postamble of the figure/document/subfigure
|
||||
environment, use the keyword arguments: `figure_wrapper`, `document_wrapper`,
|
||||
`subfigure_wrapper`. The default values are stored in private variables
|
||||
e.g. ``nx.nx_layout._DOCUMENT_WRAPPER``
|
||||
|
||||
References
|
||||
----------
|
||||
TikZ: https://tikz.dev/
|
||||
|
||||
TikZ options details: https://tikz.dev/tikz-actions
|
||||
"""
|
||||
import numbers
|
||||
import os
|
||||
|
||||
import networkx as nx
|
||||
|
||||
__all__ = [
|
||||
"to_latex_raw",
|
||||
"to_latex",
|
||||
"write_latex",
|
||||
]
|
||||
|
||||
|
||||
@nx.utils.not_implemented_for("multigraph")
|
||||
def to_latex_raw(
|
||||
G,
|
||||
pos="pos",
|
||||
tikz_options="",
|
||||
default_node_options="",
|
||||
node_options="node_options",
|
||||
node_label="label",
|
||||
default_edge_options="",
|
||||
edge_options="edge_options",
|
||||
edge_label="label",
|
||||
edge_label_options="edge_label_options",
|
||||
):
|
||||
"""Return a string of the LaTeX/TikZ code to draw `G`
|
||||
|
||||
This function produces just the code for the tikzpicture
|
||||
without any enclosing environment.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
G : NetworkX graph
|
||||
The NetworkX graph to be drawn
|
||||
pos : string or dict (default "pos")
|
||||
The name of the node attribute on `G` that holds the position of each node.
|
||||
Positions can be sequences of length 2 with numbers for (x,y) coordinates.
|
||||
They can also be strings to denote positions in TikZ style, such as (x, y)
|
||||
or (angle:radius).
|
||||
If a dict, it should be keyed by node to a position.
|
||||
If an empty dict, a circular layout is computed by TikZ.
|
||||
tikz_options : string
|
||||
The tikzpicture options description defining the options for the picture.
|
||||
Often large scale options like `[scale=2]`.
|
||||
default_node_options : string
|
||||
The draw options for a path of nodes. Individual node options override these.
|
||||
node_options : string or dict
|
||||
The name of the node attribute on `G` that holds the options for each node.
|
||||
Or a dict keyed by node to a string holding the options for that node.
|
||||
node_label : string or dict
|
||||
The name of the node attribute on `G` that holds the node label (text)
|
||||
displayed for each node. If the attribute is "" or not present, the node
|
||||
itself is drawn as a string. LaTeX processing such as ``"$A_1$"`` is allowed.
|
||||
Or a dict keyed by node to a string holding the label for that node.
|
||||
default_edge_options : string
|
||||
The options for the scope drawing all edges. The default is "[-]" for
|
||||
undirected graphs and "[->]" for directed graphs.
|
||||
edge_options : string or dict
|
||||
The name of the edge attribute on `G` that holds the options for each edge.
|
||||
If the edge is a self-loop and ``"loop" not in edge_options`` the option
|
||||
"loop," is added to the options for the self-loop edge. Hence you can
|
||||
use "[loop above]" explicitly, but the default is "[loop]".
|
||||
Or a dict keyed by edge to a string holding the options for that edge.
|
||||
edge_label : string or dict
|
||||
The name of the edge attribute on `G` that holds the edge label (text)
|
||||
displayed for each edge. If the attribute is "" or not present, no edge
|
||||
label is drawn.
|
||||
Or a dict keyed by edge to a string holding the label for that edge.
|
||||
edge_label_options : string or dict
|
||||
The name of the edge attribute on `G` that holds the label options for
|
||||
each edge. For example, "[sloped,above,blue]". The default is no options.
|
||||
Or a dict keyed by edge to a string holding the label options for that edge.
|
||||
|
||||
Returns
|
||||
=======
|
||||
latex_code : string
|
||||
The text string which draws the desired graph(s) when compiled by LaTeX.
|
||||
|
||||
See Also
|
||||
========
|
||||
to_latex
|
||||
write_latex
|
||||
"""
|
||||
i4 = "\n "
|
||||
i8 = "\n "
|
||||
|
||||
# set up position dict
|
||||
# TODO allow pos to be None and use a nice TikZ default
|
||||
if not isinstance(pos, dict):
|
||||
pos = nx.get_node_attributes(G, pos)
|
||||
if not pos:
|
||||
# circular layout with radius 2
|
||||
pos = {n: f"({round(360.0 * i / len(G), 3)}:2)" for i, n in enumerate(G)}
|
||||
for node in G:
|
||||
if node not in pos:
|
||||
raise nx.NetworkXError(f"node {node} has no specified pos {pos}")
|
||||
posnode = pos[node]
|
||||
if not isinstance(posnode, str):
|
||||
try:
|
||||
posx, posy = posnode
|
||||
pos[node] = f"({round(posx, 3)}, {round(posy, 3)})"
|
||||
except (TypeError, ValueError):
|
||||
msg = f"position pos[{node}] is not 2-tuple or a string: {posnode}"
|
||||
raise nx.NetworkXError(msg)
|
||||
|
||||
# set up all the dicts
|
||||
if not isinstance(node_options, dict):
|
||||
node_options = nx.get_node_attributes(G, node_options)
|
||||
if not isinstance(node_label, dict):
|
||||
node_label = nx.get_node_attributes(G, node_label)
|
||||
if not isinstance(edge_options, dict):
|
||||
edge_options = nx.get_edge_attributes(G, edge_options)
|
||||
if not isinstance(edge_label, dict):
|
||||
edge_label = nx.get_edge_attributes(G, edge_label)
|
||||
if not isinstance(edge_label_options, dict):
|
||||
edge_label_options = nx.get_edge_attributes(G, edge_label_options)
|
||||
|
||||
# process default options (add brackets or not)
|
||||
topts = "" if tikz_options == "" else f"[{tikz_options.strip('[]')}]"
|
||||
defn = "" if default_node_options == "" else f"[{default_node_options.strip('[]')}]"
|
||||
linestyle = f"{'->' if G.is_directed() else '-'}"
|
||||
if default_edge_options == "":
|
||||
defe = "[" + linestyle + "]"
|
||||
elif "-" in default_edge_options:
|
||||
defe = default_edge_options
|
||||
else:
|
||||
defe = f"[{linestyle},{default_edge_options.strip('[]')}]"
|
||||
|
||||
# Construct the string line by line
|
||||
result = " \\begin{tikzpicture}" + topts
|
||||
result += i4 + " \\draw" + defn
|
||||
# load the nodes
|
||||
for n in G:
|
||||
# node options goes inside square brackets
|
||||
nopts = f"[{node_options[n].strip('[]')}]" if n in node_options else ""
|
||||
# node text goes inside curly brackets {}
|
||||
ntext = f"{{{node_label[n]}}}" if n in node_label else f"{{{n}}}"
|
||||
|
||||
result += i8 + f"{pos[n]} node{nopts} ({n}){ntext}"
|
||||
result += ";\n"
|
||||
|
||||
# load the edges
|
||||
result += " \\begin{scope}" + defe
|
||||
for edge in G.edges:
|
||||
u, v = edge[:2]
|
||||
e_opts = f"{edge_options[edge]}".strip("[]") if edge in edge_options else ""
|
||||
# add loop options for selfloops if not present
|
||||
if u == v and "loop" not in e_opts:
|
||||
e_opts = "loop," + e_opts
|
||||
e_opts = f"[{e_opts}]" if e_opts != "" else ""
|
||||
# TODO -- handle bending of multiedges
|
||||
|
||||
els = edge_label_options[edge] if edge in edge_label_options else ""
|
||||
# edge label options goes inside square brackets []
|
||||
els = f"[{els.strip('[]')}]"
|
||||
# edge text is drawn using the TikZ node command inside curly brackets {}
|
||||
e_label = f" node{els} {{{edge_label[edge]}}}" if edge in edge_label else ""
|
||||
|
||||
result += i8 + f"\\draw{e_opts} ({u}) to{e_label} ({v});"
|
||||
|
||||
result += "\n \\end{scope}\n \\end{tikzpicture}\n"
|
||||
return result
|
||||
|
||||
|
||||
_DOC_WRAPPER_TIKZ = r"""\documentclass{{report}}
|
||||
\usepackage{{tikz}}
|
||||
\usepackage{{subcaption}}
|
||||
|
||||
\begin{{document}}
|
||||
{content}
|
||||
\end{{document}}"""
|
||||
|
||||
|
||||
_FIG_WRAPPER = r"""\begin{{figure}}
|
||||
{content}{caption}{label}
|
||||
\end{{figure}}"""
|
||||
|
||||
|
||||
_SUBFIG_WRAPPER = r""" \begin{{subfigure}}{{{size}\textwidth}}
|
||||
{content}{caption}{label}
|
||||
\end{{subfigure}}"""
|
||||
|
||||
|
||||
def to_latex(
|
||||
Gbunch,
|
||||
pos="pos",
|
||||
tikz_options="",
|
||||
default_node_options="",
|
||||
node_options="node_options",
|
||||
node_label="node_label",
|
||||
default_edge_options="",
|
||||
edge_options="edge_options",
|
||||
edge_label="edge_label",
|
||||
edge_label_options="edge_label_options",
|
||||
caption="",
|
||||
latex_label="",
|
||||
sub_captions=None,
|
||||
sub_labels=None,
|
||||
n_rows=1,
|
||||
as_document=True,
|
||||
document_wrapper=_DOC_WRAPPER_TIKZ,
|
||||
figure_wrapper=_FIG_WRAPPER,
|
||||
subfigure_wrapper=_SUBFIG_WRAPPER,
|
||||
):
|
||||
"""Return latex code to draw the graph(s) in `Gbunch`
|
||||
|
||||
The TikZ drawing utility in LaTeX is used to draw the graph(s).
|
||||
If `Gbunch` is a graph, it is drawn in a figure environment.
|
||||
If `Gbunch` is an iterable of graphs, each is drawn in a subfigure environment
|
||||
within a single figure environment.
|
||||
|
||||
If `as_document` is True, the figure is wrapped inside a document environment
|
||||
so that the resulting string is ready to be compiled by LaTeX. Otherwise,
|
||||
the string is ready for inclusion in a larger tex document using ``\\include``
|
||||
or ``\\input`` statements.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
Gbunch : NetworkX graph or iterable of NetworkX graphs
|
||||
The NetworkX graph to be drawn or an iterable of graphs
|
||||
to be drawn inside subfigures of a single figure.
|
||||
pos : string or list of strings
|
||||
The name of the node attribute on `G` that holds the position of each node.
|
||||
Positions can be sequences of length 2 with numbers for (x,y) coordinates.
|
||||
They can also be strings to denote positions in TikZ style, such as (x, y)
|
||||
or (angle:radius).
|
||||
If a dict, it should be keyed by node to a position.
|
||||
If an empty dict, a circular layout is computed by TikZ.
|
||||
If you are drawing many graphs in subfigures, use a list of position dicts.
|
||||
tikz_options : string
|
||||
The tikzpicture options description defining the options for the picture.
|
||||
Often large scale options like `[scale=2]`.
|
||||
default_node_options : string
|
||||
The draw options for a path of nodes. Individual node options override these.
|
||||
node_options : string or dict
|
||||
The name of the node attribute on `G` that holds the options for each node.
|
||||
Or a dict keyed by node to a string holding the options for that node.
|
||||
node_label : string or dict
|
||||
The name of the node attribute on `G` that holds the node label (text)
|
||||
displayed for each node. If the attribute is "" or not present, the node
|
||||
itself is drawn as a string. LaTeX processing such as ``"$A_1$"`` is allowed.
|
||||
Or a dict keyed by node to a string holding the label for that node.
|
||||
default_edge_options : string
|
||||
The options for the scope drawing all edges. The default is "[-]" for
|
||||
undirected graphs and "[->]" for directed graphs.
|
||||
edge_options : string or dict
|
||||
The name of the edge attribute on `G` that holds the options for each edge.
|
||||
If the edge is a self-loop and ``"loop" not in edge_options`` the option
|
||||
"loop," is added to the options for the self-loop edge. Hence you can
|
||||
use "[loop above]" explicitly, but the default is "[loop]".
|
||||
Or a dict keyed by edge to a string holding the options for that edge.
|
||||
edge_label : string or dict
|
||||
The name of the edge attribute on `G` that holds the edge label (text)
|
||||
displayed for each edge. If the attribute is "" or not present, no edge
|
||||
label is drawn.
|
||||
Or a dict keyed by edge to a string holding the label for that edge.
|
||||
edge_label_options : string or dict
|
||||
The name of the edge attribute on `G` that holds the label options for
|
||||
each edge. For example, "[sloped,above,blue]". The default is no options.
|
||||
Or a dict keyed by edge to a string holding the label options for that edge.
|
||||
caption : string
|
||||
The caption string for the figure environment
|
||||
latex_label : string
|
||||
The latex label used for the figure for easy referral from the main text
|
||||
sub_captions : list of strings
|
||||
The sub_caption string for each subfigure in the figure
|
||||
sub_latex_labels : list of strings
|
||||
The latex label for each subfigure in the figure
|
||||
n_rows : int
|
||||
The number of rows of subfigures to arrange for multiple graphs
|
||||
as_document : bool
|
||||
Whether to wrap the latex code in a document environment for compiling
|
||||
document_wrapper : formatted text string with variable ``content``.
|
||||
This text is called to evaluate the content embedded in a document
|
||||
environment with a preamble setting up TikZ.
|
||||
figure_wrapper : formatted text string
|
||||
This text is evaluated with variables ``content``, ``caption`` and ``label``.
|
||||
It wraps the content and if a caption is provided, adds the latex code for
|
||||
that caption, and if a label is provided, adds the latex code for a label.
|
||||
subfigure_wrapper : formatted text string
|
||||
This text evaluate variables ``size``, ``content``, ``caption`` and ``label``.
|
||||
It wraps the content and if a caption is provided, adds the latex code for
|
||||
that caption, and if a label is provided, adds the latex code for a label.
|
||||
The size is the vertical size of each row of subfigures as a fraction.
|
||||
|
||||
Returns
|
||||
=======
|
||||
latex_code : string
|
||||
The text string which draws the desired graph(s) when compiled by LaTeX.
|
||||
|
||||
See Also
|
||||
========
|
||||
write_latex
|
||||
to_latex_raw
|
||||
"""
|
||||
if hasattr(Gbunch, "adj"):
|
||||
raw = to_latex_raw(
|
||||
Gbunch,
|
||||
pos,
|
||||
tikz_options,
|
||||
default_node_options,
|
||||
node_options,
|
||||
node_label,
|
||||
default_edge_options,
|
||||
edge_options,
|
||||
edge_label,
|
||||
edge_label_options,
|
||||
)
|
||||
else: # iterator of graphs
|
||||
sbf = subfigure_wrapper
|
||||
size = 1 / n_rows
|
||||
|
||||
N = len(Gbunch)
|
||||
if isinstance(pos, (str, dict)):
|
||||
pos = [pos] * N
|
||||
if sub_captions is None:
|
||||
sub_captions = [""] * N
|
||||
if sub_labels is None:
|
||||
sub_labels = [""] * N
|
||||
if not (len(Gbunch) == len(pos) == len(sub_captions) == len(sub_labels)):
|
||||
raise nx.NetworkXError(
|
||||
"length of Gbunch, sub_captions and sub_figures must agree"
|
||||
)
|
||||
|
||||
raw = ""
|
||||
for G, pos, subcap, sublbl in zip(Gbunch, pos, sub_captions, sub_labels):
|
||||
subraw = to_latex_raw(
|
||||
G,
|
||||
pos,
|
||||
tikz_options,
|
||||
default_node_options,
|
||||
node_options,
|
||||
node_label,
|
||||
default_edge_options,
|
||||
edge_options,
|
||||
edge_label,
|
||||
edge_label_options,
|
||||
)
|
||||
cap = f" \\caption{{{subcap}}}" if subcap else ""
|
||||
lbl = f"\\label{{{sublbl}}}" if sublbl else ""
|
||||
raw += sbf.format(size=size, content=subraw, caption=cap, label=lbl)
|
||||
raw += "\n"
|
||||
|
||||
# put raw latex code into a figure environment and optionally into a document
|
||||
raw = raw[:-1]
|
||||
cap = f"\n \\caption{{{caption}}}" if caption else ""
|
||||
lbl = f"\\label{{{latex_label}}}" if latex_label else ""
|
||||
fig = figure_wrapper.format(content=raw, caption=cap, label=lbl)
|
||||
if as_document:
|
||||
return document_wrapper.format(content=fig)
|
||||
return fig
|
||||
|
||||
|
||||
@nx.utils.open_file(1, mode="w")
|
||||
def write_latex(Gbunch, path, **options):
|
||||
"""Write the latex code to draw the graph(s) onto `path`.
|
||||
|
||||
This convenience function creates the latex drawing code as a string
|
||||
and writes that to a file ready to be compiled when `as_document` is True
|
||||
or ready to be ``import`` ed or ``include`` ed into your main LaTeX document.
|
||||
|
||||
The `path` argument can be a string filename or a file handle to write to.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
Gbunch : NetworkX graph or iterable of NetworkX graphs
|
||||
If Gbunch is a graph, it is drawn in a figure environment.
|
||||
If Gbunch is an iterable of graphs, each is drawn in a subfigure
|
||||
environment within a single figure environment.
|
||||
path : filename
|
||||
Filename or file handle to write to
|
||||
options : dict
|
||||
By default, TikZ is used with options: (others are ignored)::
|
||||
|
||||
pos : string or dict or list
|
||||
The name of the node attribute on `G` that holds the position of each node.
|
||||
Positions can be sequences of length 2 with numbers for (x,y) coordinates.
|
||||
They can also be strings to denote positions in TikZ style, such as (x, y)
|
||||
or (angle:radius).
|
||||
If a dict, it should be keyed by node to a position.
|
||||
If an empty dict, a circular layout is computed by TikZ.
|
||||
If you are drawing many graphs in subfigures, use a list of position dicts.
|
||||
tikz_options : string
|
||||
The tikzpicture options description defining the options for the picture.
|
||||
Often large scale options like `[scale=2]`.
|
||||
default_node_options : string
|
||||
The draw options for a path of nodes. Individual node options override these.
|
||||
node_options : string or dict
|
||||
The name of the node attribute on `G` that holds the options for each node.
|
||||
Or a dict keyed by node to a string holding the options for that node.
|
||||
node_label : string or dict
|
||||
The name of the node attribute on `G` that holds the node label (text)
|
||||
displayed for each node. If the attribute is "" or not present, the node
|
||||
itself is drawn as a string. LaTeX processing such as ``"$A_1$"`` is allowed.
|
||||
Or a dict keyed by node to a string holding the label for that node.
|
||||
default_edge_options : string
|
||||
The options for the scope drawing all edges. The default is "[-]" for
|
||||
undirected graphs and "[->]" for directed graphs.
|
||||
edge_options : string or dict
|
||||
The name of the edge attribute on `G` that holds the options for each edge.
|
||||
If the edge is a self-loop and ``"loop" not in edge_options`` the option
|
||||
"loop," is added to the options for the self-loop edge. Hence you can
|
||||
use "[loop above]" explicitly, but the default is "[loop]".
|
||||
Or a dict keyed by edge to a string holding the options for that edge.
|
||||
edge_label : string or dict
|
||||
The name of the edge attribute on `G` that holds the edge label (text)
|
||||
displayed for each edge. If the attribute is "" or not present, no edge
|
||||
label is drawn.
|
||||
Or a dict keyed by edge to a string holding the label for that edge.
|
||||
edge_label_options : string or dict
|
||||
The name of the edge attribute on `G` that holds the label options for
|
||||
each edge. For example, "[sloped,above,blue]". The default is no options.
|
||||
Or a dict keyed by edge to a string holding the label options for that edge.
|
||||
caption : string
|
||||
The caption string for the figure environment
|
||||
latex_label : string
|
||||
The latex label used for the figure for easy referral from the main text
|
||||
sub_captions : list of strings
|
||||
The sub_caption string for each subfigure in the figure
|
||||
sub_latex_labels : list of strings
|
||||
The latex label for each subfigure in the figure
|
||||
n_rows : int
|
||||
The number of rows of subfigures to arrange for multiple graphs
|
||||
as_document : bool
|
||||
Whether to wrap the latex code in a document environment for compiling
|
||||
document_wrapper : formatted text string with variable ``content``.
|
||||
This text is called to evaluate the content embedded in a document
|
||||
environment with a preamble setting up the TikZ syntax.
|
||||
figure_wrapper : formatted text string
|
||||
This text is evaluated with variables ``content``, ``caption`` and ``label``.
|
||||
It wraps the content and if a caption is provided, adds the latex code for
|
||||
that caption, and if a label is provided, adds the latex code for a label.
|
||||
subfigure_wrapper : formatted text string
|
||||
This text evaluate variables ``size``, ``content``, ``caption`` and ``label``.
|
||||
It wraps the content and if a caption is provided, adds the latex code for
|
||||
that caption, and if a label is provided, adds the latex code for a label.
|
||||
The size is the vertical size of each row of subfigures as a fraction.
|
||||
|
||||
See Also
|
||||
========
|
||||
to_latex
|
||||
"""
|
||||
path.write(to_latex(Gbunch, **options))
|
||||
452
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_pydot.py
vendored
Normal file
452
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_pydot.py
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
"""
|
||||
*****
|
||||
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()
|
||||
|
||||
# List of one or more "pydot.Dot" instances deserialized from this file.
|
||||
P_list = pydot.graph_from_dot_data(data)
|
||||
|
||||
# Convert only the first such instance into a NetworkX graph.
|
||||
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): # pydot bug: get_strict() shouldn't take argument
|
||||
multiedges = False
|
||||
else:
|
||||
multiedges = True
|
||||
|
||||
if P.get_type() == "graph": # undirected
|
||||
if multiedges:
|
||||
N = nx.MultiGraph()
|
||||
else:
|
||||
N = nx.Graph()
|
||||
else:
|
||||
if multiedges:
|
||||
N = nx.MultiDiGraph()
|
||||
else:
|
||||
N = nx.DiGraph()
|
||||
|
||||
# assign defaults
|
||||
name = P.get_name().strip('"')
|
||||
if name != "":
|
||||
N.name = name
|
||||
|
||||
# add nodes, attributes to N.node_attr
|
||||
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())
|
||||
|
||||
# add edges
|
||||
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)
|
||||
|
||||
# add default attributes for graph, nodes, edges
|
||||
pattr = P.get_attributes()
|
||||
if pattr:
|
||||
N.graph["graph"] = pattr
|
||||
try:
|
||||
N.graph["node"] = P.get_node_defaults()[0]
|
||||
except (IndexError, TypeError):
|
||||
pass # N.graph['node']={}
|
||||
try:
|
||||
N.graph["edge"] = P.get_edge_defaults()[0]
|
||||
except (IndexError, TypeError):
|
||||
pass # N.graph['edge']={}
|
||||
return N
|
||||
|
||||
|
||||
def _check_colon_quotes(s):
|
||||
# A quick helper function to check if a string has a colon in it
|
||||
# and if it is quoted properly with double quotes.
|
||||
# refer https://github.com/pydot/pydot/issues/258
|
||||
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)
|
||||
|
||||
# set Graphviz graph type
|
||||
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()}
|
||||
# Explicitly catch nodes with ":" in node names or nodedata.
|
||||
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))
|
||||
|
||||
# List of low-level bytes comprising a string in the dot language converted
|
||||
# from the passed graph with the passed external GraphViz command.
|
||||
D_bytes = P.create_dot(prog=prog)
|
||||
|
||||
# Unique string decoded from these bytes with the preferred locale encoding
|
||||
D = str(D_bytes, encoding=getpreferredencoding())
|
||||
|
||||
if D == "": # no data returned
|
||||
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
|
||||
|
||||
# List of one or more "pydot.Dot" instances deserialized from this string.
|
||||
Q_list = pydot.graph_from_dot_data(D)
|
||||
assert len(Q_list) == 1
|
||||
|
||||
# The first and only such instance, as guaranteed by the above assertion.
|
||||
Q = Q_list[0]
|
||||
|
||||
node_pos = {}
|
||||
for n in G.nodes():
|
||||
str_n = str(n)
|
||||
# Explicitly catch nodes with ":" in node names or nodedata.
|
||||
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] # strip leading and trailing double quotes
|
||||
if pos is not None:
|
||||
xx, yy = pos.split(",")
|
||||
node_pos[n] = (float(xx), float(yy))
|
||||
return node_pos
|
||||
1552
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_pylab.py
vendored
Normal file
1552
.CondaPkg/env/Lib/site-packages/networkx/drawing/nx_pylab.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
0
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__init__.py
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_agraph.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_agraph.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_latex.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_latex.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_layout.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_layout.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_pydot.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_pydot.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_pylab.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/__pycache__/test_pylab.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/baseline/test_house_with_colors.png
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/baseline/test_house_with_colors.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
242
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_agraph.py
vendored
Normal file
242
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_agraph.py
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
"""Unit tests for PyGraphviz interface."""
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
pygraphviz = pytest.importorskip("pygraphviz")
|
||||
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import edges_equal, graphs_equal, nodes_equal
|
||||
|
||||
|
||||
class TestAGraph:
|
||||
def build_graph(self, G):
|
||||
edges = [("A", "B"), ("A", "C"), ("A", "C"), ("B", "C"), ("A", "D")]
|
||||
G.add_edges_from(edges)
|
||||
G.add_node("E")
|
||||
G.graph["metal"] = "bronze"
|
||||
return G
|
||||
|
||||
def assert_equal(self, G1, G2):
|
||||
assert nodes_equal(G1.nodes(), G2.nodes())
|
||||
assert edges_equal(G1.edges(), G2.edges())
|
||||
assert G1.graph["metal"] == G2.graph["metal"]
|
||||
|
||||
def agraph_checks(self, G):
|
||||
G = self.build_graph(G)
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
H = nx.nx_agraph.from_agraph(A)
|
||||
self.assert_equal(G, H)
|
||||
|
||||
fd, fname = tempfile.mkstemp()
|
||||
nx.drawing.nx_agraph.write_dot(H, fname)
|
||||
Hin = nx.nx_agraph.read_dot(fname)
|
||||
self.assert_equal(H, Hin)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
(fd, fname) = tempfile.mkstemp()
|
||||
with open(fname, "w") as fh:
|
||||
nx.drawing.nx_agraph.write_dot(H, fh)
|
||||
|
||||
with open(fname) as fh:
|
||||
Hin = nx.nx_agraph.read_dot(fh)
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
self.assert_equal(H, Hin)
|
||||
|
||||
def test_from_agraph_name(self):
|
||||
G = nx.Graph(name="test")
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
H = nx.nx_agraph.from_agraph(A)
|
||||
assert G.name == "test"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"graph_class", (nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph)
|
||||
)
|
||||
def test_from_agraph_create_using(self, graph_class):
|
||||
G = nx.path_graph(3)
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
H = nx.nx_agraph.from_agraph(A, create_using=graph_class)
|
||||
assert isinstance(H, graph_class)
|
||||
|
||||
def test_from_agraph_named_edges(self):
|
||||
# Create an AGraph from an existing (non-multi) Graph
|
||||
G = nx.Graph()
|
||||
G.add_nodes_from([0, 1])
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
# Add edge (+ name, given by key) to the AGraph
|
||||
A.add_edge(0, 1, key="foo")
|
||||
# Verify a.name roundtrips out to 'key' in from_agraph
|
||||
H = nx.nx_agraph.from_agraph(A)
|
||||
assert isinstance(H, nx.Graph)
|
||||
assert ("0", "1", {"key": "foo"}) in H.edges(data=True)
|
||||
|
||||
def test_undirected(self):
|
||||
self.agraph_checks(nx.Graph())
|
||||
|
||||
def test_directed(self):
|
||||
self.agraph_checks(nx.DiGraph())
|
||||
|
||||
def test_multi_undirected(self):
|
||||
self.agraph_checks(nx.MultiGraph())
|
||||
|
||||
def test_multi_directed(self):
|
||||
self.agraph_checks(nx.MultiDiGraph())
|
||||
|
||||
def test_to_agraph_with_nodedata(self):
|
||||
G = nx.Graph()
|
||||
G.add_node(1, color="red")
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
assert dict(A.nodes()[0].attr) == {"color": "red"}
|
||||
|
||||
@pytest.mark.parametrize("graph_class", (nx.Graph, nx.MultiGraph))
|
||||
def test_to_agraph_with_edgedata(self, graph_class):
|
||||
G = graph_class()
|
||||
G.add_nodes_from([0, 1])
|
||||
G.add_edge(0, 1, color="yellow")
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
assert dict(A.edges()[0].attr) == {"color": "yellow"}
|
||||
|
||||
def test_view_pygraphviz_path(self, tmp_path):
|
||||
G = nx.complete_graph(3)
|
||||
input_path = str(tmp_path / "graph.png")
|
||||
out_path, A = nx.nx_agraph.view_pygraphviz(G, path=input_path, show=False)
|
||||
assert out_path == input_path
|
||||
# Ensure file is not empty
|
||||
with open(input_path, "rb") as fh:
|
||||
data = fh.read()
|
||||
assert len(data) > 0
|
||||
|
||||
def test_view_pygraphviz_file_suffix(self, tmp_path):
|
||||
G = nx.complete_graph(3)
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, suffix=1, show=False)
|
||||
assert path[-6:] == "_1.png"
|
||||
|
||||
def test_view_pygraphviz(self):
|
||||
G = nx.Graph() # "An empty graph cannot be drawn."
|
||||
pytest.raises(nx.NetworkXException, nx.nx_agraph.view_pygraphviz, G)
|
||||
G = nx.barbell_graph(4, 6)
|
||||
nx.nx_agraph.view_pygraphviz(G, show=False)
|
||||
|
||||
def test_view_pygraphviz_edgelabel(self):
|
||||
G = nx.Graph()
|
||||
G.add_edge(1, 2, weight=7)
|
||||
G.add_edge(2, 3, weight=8)
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="weight", show=False)
|
||||
for edge in A.edges():
|
||||
assert edge.attr["weight"] in ("7", "8")
|
||||
|
||||
def test_view_pygraphviz_callable_edgelabel(self):
|
||||
G = nx.complete_graph(3)
|
||||
|
||||
def foo_label(data):
|
||||
return "foo"
|
||||
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel=foo_label, show=False)
|
||||
for edge in A.edges():
|
||||
assert edge.attr["label"] == "foo"
|
||||
|
||||
def test_view_pygraphviz_multigraph_edgelabels(self):
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, key=0, name="left_fork")
|
||||
G.add_edge(0, 1, key=1, name="right_fork")
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, edgelabel="name", show=False)
|
||||
edges = A.edges()
|
||||
assert len(edges) == 2
|
||||
for edge in edges:
|
||||
assert edge.attr["label"].strip() in ("left_fork", "right_fork")
|
||||
|
||||
def test_graph_with_reserved_keywords(self):
|
||||
# test attribute/keyword clash case for #1582
|
||||
# node: n
|
||||
# edges: u,v
|
||||
G = nx.Graph()
|
||||
G = self.build_graph(G)
|
||||
G.nodes["E"]["n"] = "keyword"
|
||||
G.edges[("A", "B")]["u"] = "keyword"
|
||||
G.edges[("A", "B")]["v"] = "keyword"
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
|
||||
def test_view_pygraphviz_no_added_attrs_to_input(self):
|
||||
G = nx.complete_graph(2)
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||||
assert G.graph == {}
|
||||
|
||||
@pytest.mark.xfail(reason="known bug in clean_attrs")
|
||||
def test_view_pygraphviz_leaves_input_graph_unmodified(self):
|
||||
G = nx.complete_graph(2)
|
||||
# Add entries to graph dict that to_agraph handles specially
|
||||
G.graph["node"] = {"width": "0.80"}
|
||||
G.graph["edge"] = {"fontsize": "14"}
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||||
assert G.graph == {"node": {"width": "0.80"}, "edge": {"fontsize": "14"}}
|
||||
|
||||
def test_graph_with_AGraph_attrs(self):
|
||||
G = nx.complete_graph(2)
|
||||
# Add entries to graph dict that to_agraph handles specially
|
||||
G.graph["node"] = {"width": "0.80"}
|
||||
G.graph["edge"] = {"fontsize": "14"}
|
||||
path, A = nx.nx_agraph.view_pygraphviz(G, show=False)
|
||||
# Ensure user-specified values are not lost
|
||||
assert dict(A.node_attr)["width"] == "0.80"
|
||||
assert dict(A.edge_attr)["fontsize"] == "14"
|
||||
|
||||
def test_round_trip_empty_graph(self):
|
||||
G = nx.Graph()
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
H = nx.nx_agraph.from_agraph(A)
|
||||
# assert graphs_equal(G, H)
|
||||
AA = nx.nx_agraph.to_agraph(H)
|
||||
HH = nx.nx_agraph.from_agraph(AA)
|
||||
assert graphs_equal(H, HH)
|
||||
G.graph["graph"] = {}
|
||||
G.graph["node"] = {}
|
||||
G.graph["edge"] = {}
|
||||
assert graphs_equal(G, HH)
|
||||
|
||||
@pytest.mark.xfail(reason="integer->string node conversion in round trip")
|
||||
def test_round_trip_integer_nodes(self):
|
||||
G = nx.complete_graph(3)
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
H = nx.nx_agraph.from_agraph(A)
|
||||
assert graphs_equal(G, H)
|
||||
|
||||
def test_graphviz_alias(self):
|
||||
G = self.build_graph(nx.Graph())
|
||||
pos_graphviz = nx.nx_agraph.graphviz_layout(G)
|
||||
pos_pygraphviz = nx.nx_agraph.pygraphviz_layout(G)
|
||||
assert pos_graphviz == pos_pygraphviz
|
||||
|
||||
@pytest.mark.parametrize("root", range(5))
|
||||
def test_pygraphviz_layout_root(self, root):
|
||||
# NOTE: test depends on layout prog being deterministic
|
||||
G = nx.complete_graph(5)
|
||||
A = nx.nx_agraph.to_agraph(G)
|
||||
# Get layout with root arg is not None
|
||||
pygv_layout = nx.nx_agraph.pygraphviz_layout(G, prog="circo", root=root)
|
||||
# Equivalent layout directly on AGraph
|
||||
A.layout(args=f"-Groot={root}", prog="circo")
|
||||
# Parse AGraph layout
|
||||
a1_pos = tuple(float(v) for v in dict(A.get_node("1").attr)["pos"].split(","))
|
||||
assert pygv_layout[1] == a1_pos
|
||||
|
||||
def test_2d_layout(self):
|
||||
G = nx.Graph()
|
||||
G = self.build_graph(G)
|
||||
G.graph["dimen"] = 2
|
||||
pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
|
||||
pos = list(pos.values())
|
||||
assert len(pos) == 5
|
||||
assert len(pos[0]) == 2
|
||||
|
||||
def test_3d_layout(self):
|
||||
G = nx.Graph()
|
||||
G = self.build_graph(G)
|
||||
G.graph["dimen"] = 3
|
||||
pos = nx.nx_agraph.pygraphviz_layout(G, prog="neato")
|
||||
pos = list(pos.values())
|
||||
assert len(pos) == 5
|
||||
assert len(pos[0]) == 3
|
||||
292
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_latex.py
vendored
Normal file
292
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_latex.py
vendored
Normal file
@@ -0,0 +1,292 @@
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def test_tikz_attributes():
|
||||
G = nx.path_graph(4, create_using=nx.DiGraph)
|
||||
pos = {n: (n, n) for n in G}
|
||||
|
||||
G.add_edge(0, 0)
|
||||
G.edges[(0, 0)]["label"] = "Loop"
|
||||
G.edges[(0, 0)]["label_options"] = "midway"
|
||||
|
||||
G.nodes[0]["style"] = "blue"
|
||||
G.nodes[1]["style"] = "line width=3,draw"
|
||||
G.nodes[2]["style"] = "circle,draw,blue!50"
|
||||
G.nodes[3]["label"] = "Stop"
|
||||
G.edges[(0, 1)]["label"] = "1st Step"
|
||||
G.edges[(0, 1)]["label_options"] = "near end"
|
||||
G.edges[(2, 3)]["label"] = "3rd Step"
|
||||
G.edges[(2, 3)]["label_options"] = "near start"
|
||||
G.edges[(2, 3)]["style"] = "bend left,green"
|
||||
G.edges[(1, 2)]["label"] = "2nd"
|
||||
G.edges[(1, 2)]["label_options"] = "pos=0.5"
|
||||
G.edges[(1, 2)]["style"] = ">->,bend right,line width=3,green!90"
|
||||
|
||||
output_tex = nx.to_latex(
|
||||
G,
|
||||
pos=pos,
|
||||
as_document=False,
|
||||
tikz_options="[scale=3]",
|
||||
node_options="style",
|
||||
edge_options="style",
|
||||
node_label="label",
|
||||
edge_label="label",
|
||||
edge_label_options="label_options",
|
||||
)
|
||||
expected_tex = r"""\begin{figure}
|
||||
\begin{tikzpicture}[scale=3]
|
||||
\draw
|
||||
(0, 0) node[blue] (0){0}
|
||||
(1, 1) node[line width=3,draw] (1){1}
|
||||
(2, 2) node[circle,draw,blue!50] (2){2}
|
||||
(3, 3) node (3){Stop};
|
||||
\begin{scope}[->]
|
||||
\draw (0) to node[near end] {1st Step} (1);
|
||||
\draw[loop,] (0) to node[midway] {Loop} (0);
|
||||
\draw[>->,bend right,line width=3,green!90] (1) to node[pos=0.5] {2nd} (2);
|
||||
\draw[bend left,green] (2) to node[near start] {3rd Step} (3);
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\end{figure}"""
|
||||
|
||||
assert output_tex == expected_tex
|
||||
# print(output_tex)
|
||||
# # Pretty way to assert that A.to_document() == expected_tex
|
||||
# content_same = True
|
||||
# for aa, bb in zip(expected_tex.split("\n"), output_tex.split("\n")):
|
||||
# if aa != bb:
|
||||
# content_same = False
|
||||
# print(f"-{aa}|\n+{bb}|")
|
||||
# assert content_same
|
||||
|
||||
|
||||
def test_basic_multiple_graphs():
|
||||
H1 = nx.path_graph(4)
|
||||
H2 = nx.complete_graph(4)
|
||||
H3 = nx.path_graph(8)
|
||||
H4 = nx.complete_graph(8)
|
||||
captions = [
|
||||
"Path on 4 nodes",
|
||||
"Complete graph on 4 nodes",
|
||||
"Path on 8 nodes",
|
||||
"Complete graph on 8 nodes",
|
||||
]
|
||||
labels = ["fig2a", "fig2b", "fig2c", "fig2d"]
|
||||
latex_code = nx.to_latex(
|
||||
[H1, H2, H3, H4],
|
||||
n_rows=2,
|
||||
sub_captions=captions,
|
||||
sub_labels=labels,
|
||||
)
|
||||
# print(latex_code)
|
||||
assert "begin{document}" in latex_code
|
||||
assert "begin{figure}" in latex_code
|
||||
assert latex_code.count("begin{subfigure}") == 4
|
||||
assert latex_code.count("tikzpicture") == 8
|
||||
assert latex_code.count("[-]") == 4
|
||||
|
||||
|
||||
def test_basic_tikz():
|
||||
expected_tex = r"""\documentclass{report}
|
||||
\usepackage{tikz}
|
||||
\usepackage{subcaption}
|
||||
|
||||
\begin{document}
|
||||
\begin{figure}
|
||||
\begin{subfigure}{0.5\textwidth}
|
||||
\begin{tikzpicture}[scale=2]
|
||||
\draw[gray!90]
|
||||
(0.749, 0.702) node[red!90] (0){0}
|
||||
(1.0, -0.014) node[red!90] (1){1}
|
||||
(-0.777, -0.705) node (2){2}
|
||||
(-0.984, 0.042) node (3){3}
|
||||
(-0.028, 0.375) node[cyan!90] (4){4}
|
||||
(-0.412, 0.888) node (5){5}
|
||||
(0.448, -0.856) node (6){6}
|
||||
(0.003, -0.431) node[cyan!90] (7){7};
|
||||
\begin{scope}[->,gray!90]
|
||||
\draw (0) to (4);
|
||||
\draw (0) to (5);
|
||||
\draw (0) to (6);
|
||||
\draw (0) to (7);
|
||||
\draw (1) to (4);
|
||||
\draw (1) to (5);
|
||||
\draw (1) to (6);
|
||||
\draw (1) to (7);
|
||||
\draw (2) to (4);
|
||||
\draw (2) to (5);
|
||||
\draw (2) to (6);
|
||||
\draw (2) to (7);
|
||||
\draw (3) to (4);
|
||||
\draw (3) to (5);
|
||||
\draw (3) to (6);
|
||||
\draw (3) to (7);
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\caption{My tikz number 1 of 2}\label{tikz_1_2}
|
||||
\end{subfigure}
|
||||
\begin{subfigure}{0.5\textwidth}
|
||||
\begin{tikzpicture}[scale=2]
|
||||
\draw[gray!90]
|
||||
(0.749, 0.702) node[green!90] (0){0}
|
||||
(1.0, -0.014) node[green!90] (1){1}
|
||||
(-0.777, -0.705) node (2){2}
|
||||
(-0.984, 0.042) node (3){3}
|
||||
(-0.028, 0.375) node[purple!90] (4){4}
|
||||
(-0.412, 0.888) node (5){5}
|
||||
(0.448, -0.856) node (6){6}
|
||||
(0.003, -0.431) node[purple!90] (7){7};
|
||||
\begin{scope}[->,gray!90]
|
||||
\draw (0) to (4);
|
||||
\draw (0) to (5);
|
||||
\draw (0) to (6);
|
||||
\draw (0) to (7);
|
||||
\draw (1) to (4);
|
||||
\draw (1) to (5);
|
||||
\draw (1) to (6);
|
||||
\draw (1) to (7);
|
||||
\draw (2) to (4);
|
||||
\draw (2) to (5);
|
||||
\draw (2) to (6);
|
||||
\draw (2) to (7);
|
||||
\draw (3) to (4);
|
||||
\draw (3) to (5);
|
||||
\draw (3) to (6);
|
||||
\draw (3) to (7);
|
||||
\end{scope}
|
||||
\end{tikzpicture}
|
||||
\caption{My tikz number 2 of 2}\label{tikz_2_2}
|
||||
\end{subfigure}
|
||||
\caption{A graph generated with python and latex.}
|
||||
\end{figure}
|
||||
\end{document}"""
|
||||
|
||||
edges = [
|
||||
(0, 4),
|
||||
(0, 5),
|
||||
(0, 6),
|
||||
(0, 7),
|
||||
(1, 4),
|
||||
(1, 5),
|
||||
(1, 6),
|
||||
(1, 7),
|
||||
(2, 4),
|
||||
(2, 5),
|
||||
(2, 6),
|
||||
(2, 7),
|
||||
(3, 4),
|
||||
(3, 5),
|
||||
(3, 6),
|
||||
(3, 7),
|
||||
]
|
||||
G = nx.DiGraph()
|
||||
G.add_nodes_from(range(8))
|
||||
G.add_edges_from(edges)
|
||||
pos = {
|
||||
0: (0.7490296171687696, 0.702353520257394),
|
||||
1: (1.0, -0.014221357723796535),
|
||||
2: (-0.7765783344161441, -0.7054170966808919),
|
||||
3: (-0.9842690223417624, 0.04177547602465483),
|
||||
4: (-0.02768523817180917, 0.3745724439551441),
|
||||
5: (-0.41154855146767433, 0.8880106515525136),
|
||||
6: (0.44780153389148264, -0.8561492709269164),
|
||||
7: (0.0032499953371383505, -0.43092436645809945),
|
||||
}
|
||||
|
||||
rc_node_color = {0: "red!90", 1: "red!90", 4: "cyan!90", 7: "cyan!90"}
|
||||
gp_node_color = {0: "green!90", 1: "green!90", 4: "purple!90", 7: "purple!90"}
|
||||
|
||||
H = G.copy()
|
||||
nx.set_node_attributes(G, rc_node_color, "color")
|
||||
nx.set_node_attributes(H, gp_node_color, "color")
|
||||
|
||||
sub_captions = ["My tikz number 1 of 2", "My tikz number 2 of 2"]
|
||||
sub_labels = ["tikz_1_2", "tikz_2_2"]
|
||||
|
||||
output_tex = nx.to_latex(
|
||||
[G, H],
|
||||
[pos, pos],
|
||||
tikz_options="[scale=2]",
|
||||
default_node_options="gray!90",
|
||||
default_edge_options="gray!90",
|
||||
node_options="color",
|
||||
sub_captions=sub_captions,
|
||||
sub_labels=sub_labels,
|
||||
caption="A graph generated with python and latex.",
|
||||
n_rows=2,
|
||||
as_document=True,
|
||||
)
|
||||
|
||||
assert output_tex == expected_tex
|
||||
# print(output_tex)
|
||||
# # Pretty way to assert that A.to_document() == expected_tex
|
||||
# content_same = True
|
||||
# for aa, bb in zip(expected_tex.split("\n"), output_tex.split("\n")):
|
||||
# if aa != bb:
|
||||
# content_same = False
|
||||
# print(f"-{aa}|\n+{bb}|")
|
||||
# assert content_same
|
||||
|
||||
|
||||
def test_exception_pos_single_graph(to_latex=nx.to_latex):
|
||||
# smoke test that pos can be a string
|
||||
G = nx.path_graph(4)
|
||||
to_latex(G, pos="pos")
|
||||
|
||||
# must include all nodes
|
||||
pos = {0: (1, 2), 1: (0, 1), 2: (2, 1)}
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(G, pos)
|
||||
|
||||
# must have 2 values
|
||||
pos[3] = (1, 2, 3)
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(G, pos)
|
||||
pos[3] = 2
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(G, pos)
|
||||
|
||||
# check that passes with 2 values
|
||||
pos[3] = (3, 2)
|
||||
to_latex(G, pos)
|
||||
|
||||
|
||||
def test_exception_multiple_graphs(to_latex=nx.to_latex):
|
||||
G = nx.path_graph(3)
|
||||
pos_bad = {0: (1, 2), 1: (0, 1)}
|
||||
pos_OK = {0: (1, 2), 1: (0, 1), 2: (2, 1)}
|
||||
fourG = [G, G, G, G]
|
||||
fourpos = [pos_OK, pos_OK, pos_OK, pos_OK]
|
||||
|
||||
# input single dict to use for all graphs
|
||||
to_latex(fourG, pos_OK)
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(fourG, pos_bad)
|
||||
|
||||
# input list of dicts to use for all graphs
|
||||
to_latex(fourG, fourpos)
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(fourG, [pos_bad, pos_bad, pos_bad, pos_bad])
|
||||
|
||||
# every pos dict must include all nodes
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(fourG, [pos_OK, pos_OK, pos_bad, pos_OK])
|
||||
|
||||
# test sub_captions and sub_labels (len must match Gbunch)
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(fourG, fourpos, sub_captions=["hi", "hi"])
|
||||
|
||||
with pytest.raises(nx.NetworkXError):
|
||||
to_latex(fourG, fourpos, sub_labels=["hi", "hi"])
|
||||
|
||||
# all pass
|
||||
to_latex(fourG, fourpos, sub_captions=["hi"] * 4, sub_labels=["lbl"] * 4)
|
||||
|
||||
|
||||
def test_exception_multigraph():
|
||||
G = nx.path_graph(4, create_using=nx.MultiGraph)
|
||||
G.add_edge(1, 2)
|
||||
with pytest.raises(nx.NetworkXNotImplemented):
|
||||
nx.to_latex(G)
|
||||
469
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_layout.py
vendored
Normal file
469
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_layout.py
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
"""Unit tests for layout functions."""
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
pytest.importorskip("scipy")
|
||||
|
||||
|
||||
class TestLayout:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.Gi = nx.grid_2d_graph(5, 5)
|
||||
cls.Gs = nx.Graph()
|
||||
nx.add_path(cls.Gs, "abcdef")
|
||||
cls.bigG = nx.grid_2d_graph(25, 25) # > 500 nodes for sparse
|
||||
|
||||
def test_spring_fixed_without_pos(self):
|
||||
G = nx.path_graph(4)
|
||||
pytest.raises(ValueError, nx.spring_layout, G, fixed=[0])
|
||||
pos = {0: (1, 1), 2: (0, 0)}
|
||||
pytest.raises(ValueError, nx.spring_layout, G, fixed=[0, 1], pos=pos)
|
||||
nx.spring_layout(G, fixed=[0, 2], pos=pos) # No ValueError
|
||||
|
||||
def test_spring_init_pos(self):
|
||||
# Tests GH #2448
|
||||
import math
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([(0, 1), (1, 2), (2, 0), (2, 3)])
|
||||
|
||||
init_pos = {0: (0.0, 0.0)}
|
||||
fixed_pos = [0]
|
||||
pos = nx.fruchterman_reingold_layout(G, pos=init_pos, fixed=fixed_pos)
|
||||
has_nan = any(math.isnan(c) for coords in pos.values() for c in coords)
|
||||
assert not has_nan, "values should not be nan"
|
||||
|
||||
def test_smoke_empty_graph(self):
|
||||
G = []
|
||||
nx.random_layout(G)
|
||||
nx.circular_layout(G)
|
||||
nx.planar_layout(G)
|
||||
nx.spring_layout(G)
|
||||
nx.fruchterman_reingold_layout(G)
|
||||
nx.spectral_layout(G)
|
||||
nx.shell_layout(G)
|
||||
nx.bipartite_layout(G, G)
|
||||
nx.spiral_layout(G)
|
||||
nx.multipartite_layout(G)
|
||||
nx.kamada_kawai_layout(G)
|
||||
|
||||
def test_smoke_int(self):
|
||||
G = self.Gi
|
||||
nx.random_layout(G)
|
||||
nx.circular_layout(G)
|
||||
nx.planar_layout(G)
|
||||
nx.spring_layout(G)
|
||||
nx.fruchterman_reingold_layout(G)
|
||||
nx.fruchterman_reingold_layout(self.bigG)
|
||||
nx.spectral_layout(G)
|
||||
nx.spectral_layout(G.to_directed())
|
||||
nx.spectral_layout(self.bigG)
|
||||
nx.spectral_layout(self.bigG.to_directed())
|
||||
nx.shell_layout(G)
|
||||
nx.spiral_layout(G)
|
||||
nx.kamada_kawai_layout(G)
|
||||
nx.kamada_kawai_layout(G, dim=1)
|
||||
nx.kamada_kawai_layout(G, dim=3)
|
||||
nx.arf_layout(G)
|
||||
|
||||
def test_smoke_string(self):
|
||||
G = self.Gs
|
||||
nx.random_layout(G)
|
||||
nx.circular_layout(G)
|
||||
nx.planar_layout(G)
|
||||
nx.spring_layout(G)
|
||||
nx.fruchterman_reingold_layout(G)
|
||||
nx.spectral_layout(G)
|
||||
nx.shell_layout(G)
|
||||
nx.spiral_layout(G)
|
||||
nx.kamada_kawai_layout(G)
|
||||
nx.kamada_kawai_layout(G, dim=1)
|
||||
nx.kamada_kawai_layout(G, dim=3)
|
||||
nx.arf_layout(G)
|
||||
|
||||
def check_scale_and_center(self, pos, scale, center):
|
||||
center = np.array(center)
|
||||
low = center - scale
|
||||
hi = center + scale
|
||||
vpos = np.array(list(pos.values()))
|
||||
length = vpos.max(0) - vpos.min(0)
|
||||
assert (length <= 2 * scale).all()
|
||||
assert (vpos >= low).all()
|
||||
assert (vpos <= hi).all()
|
||||
|
||||
def test_scale_and_center_arg(self):
|
||||
sc = self.check_scale_and_center
|
||||
c = (4, 5)
|
||||
G = nx.complete_graph(9)
|
||||
G.add_node(9)
|
||||
sc(nx.random_layout(G, center=c), scale=0.5, center=(4.5, 5.5))
|
||||
# rest can have 2*scale length: [-scale, scale]
|
||||
sc(nx.spring_layout(G, scale=2, center=c), scale=2, center=c)
|
||||
sc(nx.spectral_layout(G, scale=2, center=c), scale=2, center=c)
|
||||
sc(nx.circular_layout(G, scale=2, center=c), scale=2, center=c)
|
||||
sc(nx.shell_layout(G, scale=2, center=c), scale=2, center=c)
|
||||
sc(nx.spiral_layout(G, scale=2, center=c), scale=2, center=c)
|
||||
sc(nx.kamada_kawai_layout(G, scale=2, center=c), scale=2, center=c)
|
||||
|
||||
c = (2, 3, 5)
|
||||
sc(nx.kamada_kawai_layout(G, dim=3, scale=2, center=c), scale=2, center=c)
|
||||
|
||||
def test_planar_layout_non_planar_input(self):
|
||||
G = nx.complete_graph(9)
|
||||
pytest.raises(nx.NetworkXException, nx.planar_layout, G)
|
||||
|
||||
def test_smoke_planar_layout_embedding_input(self):
|
||||
embedding = nx.PlanarEmbedding()
|
||||
embedding.set_data({0: [1, 2], 1: [0, 2], 2: [0, 1]})
|
||||
nx.planar_layout(embedding)
|
||||
|
||||
def test_default_scale_and_center(self):
|
||||
sc = self.check_scale_and_center
|
||||
c = (0, 0)
|
||||
G = nx.complete_graph(9)
|
||||
G.add_node(9)
|
||||
sc(nx.random_layout(G), scale=0.5, center=(0.5, 0.5))
|
||||
sc(nx.spring_layout(G), scale=1, center=c)
|
||||
sc(nx.spectral_layout(G), scale=1, center=c)
|
||||
sc(nx.circular_layout(G), scale=1, center=c)
|
||||
sc(nx.shell_layout(G), scale=1, center=c)
|
||||
sc(nx.spiral_layout(G), scale=1, center=c)
|
||||
sc(nx.kamada_kawai_layout(G), scale=1, center=c)
|
||||
|
||||
c = (0, 0, 0)
|
||||
sc(nx.kamada_kawai_layout(G, dim=3), scale=1, center=c)
|
||||
|
||||
def test_circular_planar_and_shell_dim_error(self):
|
||||
G = nx.path_graph(4)
|
||||
pytest.raises(ValueError, nx.circular_layout, G, dim=1)
|
||||
pytest.raises(ValueError, nx.shell_layout, G, dim=1)
|
||||
pytest.raises(ValueError, nx.shell_layout, G, dim=3)
|
||||
pytest.raises(ValueError, nx.planar_layout, G, dim=1)
|
||||
pytest.raises(ValueError, nx.planar_layout, G, dim=3)
|
||||
|
||||
def test_adjacency_interface_numpy(self):
|
||||
A = nx.to_numpy_array(self.Gs)
|
||||
pos = nx.drawing.layout._fruchterman_reingold(A)
|
||||
assert pos.shape == (6, 2)
|
||||
pos = nx.drawing.layout._fruchterman_reingold(A, dim=3)
|
||||
assert pos.shape == (6, 3)
|
||||
pos = nx.drawing.layout._sparse_fruchterman_reingold(A)
|
||||
assert pos.shape == (6, 2)
|
||||
|
||||
def test_adjacency_interface_scipy(self):
|
||||
A = nx.to_scipy_sparse_array(self.Gs, dtype="d")
|
||||
pos = nx.drawing.layout._sparse_fruchterman_reingold(A)
|
||||
assert pos.shape == (6, 2)
|
||||
pos = nx.drawing.layout._sparse_spectral(A)
|
||||
assert pos.shape == (6, 2)
|
||||
pos = nx.drawing.layout._sparse_fruchterman_reingold(A, dim=3)
|
||||
assert pos.shape == (6, 3)
|
||||
|
||||
def test_single_nodes(self):
|
||||
G = nx.path_graph(1)
|
||||
vpos = nx.shell_layout(G)
|
||||
assert not vpos[0].any()
|
||||
G = nx.path_graph(4)
|
||||
vpos = nx.shell_layout(G, [[0], [1, 2], [3]])
|
||||
assert not vpos[0].any()
|
||||
assert vpos[3].any() # ensure node 3 not at origin (#3188)
|
||||
assert np.linalg.norm(vpos[3]) <= 1 # ensure node 3 fits (#3753)
|
||||
vpos = nx.shell_layout(G, [[0], [1, 2], [3]], rotate=0)
|
||||
assert np.linalg.norm(vpos[3]) <= 1 # ensure node 3 fits (#3753)
|
||||
|
||||
def test_smoke_initial_pos_fruchterman_reingold(self):
|
||||
pos = nx.circular_layout(self.Gi)
|
||||
npos = nx.fruchterman_reingold_layout(self.Gi, pos=pos)
|
||||
|
||||
def test_smoke_initial_pos_arf(self):
|
||||
pos = nx.circular_layout(self.Gi)
|
||||
npos = nx.arf_layout(self.Gi, pos=pos)
|
||||
|
||||
def test_fixed_node_fruchterman_reingold(self):
|
||||
# Dense version (numpy based)
|
||||
pos = nx.circular_layout(self.Gi)
|
||||
npos = nx.spring_layout(self.Gi, pos=pos, fixed=[(0, 0)])
|
||||
assert tuple(pos[(0, 0)]) == tuple(npos[(0, 0)])
|
||||
# Sparse version (scipy based)
|
||||
pos = nx.circular_layout(self.bigG)
|
||||
npos = nx.spring_layout(self.bigG, pos=pos, fixed=[(0, 0)])
|
||||
for axis in range(2):
|
||||
assert pos[(0, 0)][axis] == pytest.approx(npos[(0, 0)][axis], abs=1e-7)
|
||||
|
||||
def test_center_parameter(self):
|
||||
G = nx.path_graph(1)
|
||||
nx.random_layout(G, center=(1, 1))
|
||||
vpos = nx.circular_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
vpos = nx.planar_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
vpos = nx.spring_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
vpos = nx.fruchterman_reingold_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
vpos = nx.spectral_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
vpos = nx.shell_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
vpos = nx.spiral_layout(G, center=(1, 1))
|
||||
assert tuple(vpos[0]) == (1, 1)
|
||||
|
||||
def test_center_wrong_dimensions(self):
|
||||
G = nx.path_graph(1)
|
||||
assert id(nx.spring_layout) == id(nx.fruchterman_reingold_layout)
|
||||
pytest.raises(ValueError, nx.random_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.circular_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.planar_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.spring_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.spring_layout, G, dim=3, center=(1, 1))
|
||||
pytest.raises(ValueError, nx.spectral_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.spectral_layout, G, dim=3, center=(1, 1))
|
||||
pytest.raises(ValueError, nx.shell_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.spiral_layout, G, center=(1, 1, 1))
|
||||
pytest.raises(ValueError, nx.kamada_kawai_layout, G, center=(1, 1, 1))
|
||||
|
||||
def test_empty_graph(self):
|
||||
G = nx.empty_graph()
|
||||
vpos = nx.random_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.circular_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.planar_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.bipartite_layout(G, G)
|
||||
assert vpos == {}
|
||||
vpos = nx.spring_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.fruchterman_reingold_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.spectral_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.shell_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.spiral_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.multipartite_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.kamada_kawai_layout(G, center=(1, 1))
|
||||
assert vpos == {}
|
||||
vpos = nx.arf_layout(G)
|
||||
assert vpos == {}
|
||||
|
||||
def test_bipartite_layout(self):
|
||||
G = nx.complete_bipartite_graph(3, 5)
|
||||
top, bottom = nx.bipartite.sets(G)
|
||||
|
||||
vpos = nx.bipartite_layout(G, top)
|
||||
assert len(vpos) == len(G)
|
||||
|
||||
top_x = vpos[list(top)[0]][0]
|
||||
bottom_x = vpos[list(bottom)[0]][0]
|
||||
for node in top:
|
||||
assert vpos[node][0] == top_x
|
||||
for node in bottom:
|
||||
assert vpos[node][0] == bottom_x
|
||||
|
||||
vpos = nx.bipartite_layout(
|
||||
G, top, align="horizontal", center=(2, 2), scale=2, aspect_ratio=1
|
||||
)
|
||||
assert len(vpos) == len(G)
|
||||
|
||||
top_y = vpos[list(top)[0]][1]
|
||||
bottom_y = vpos[list(bottom)[0]][1]
|
||||
for node in top:
|
||||
assert vpos[node][1] == top_y
|
||||
for node in bottom:
|
||||
assert vpos[node][1] == bottom_y
|
||||
|
||||
pytest.raises(ValueError, nx.bipartite_layout, G, top, align="foo")
|
||||
|
||||
def test_multipartite_layout(self):
|
||||
sizes = (0, 5, 7, 2, 8)
|
||||
G = nx.complete_multipartite_graph(*sizes)
|
||||
|
||||
vpos = nx.multipartite_layout(G)
|
||||
assert len(vpos) == len(G)
|
||||
|
||||
start = 0
|
||||
for n in sizes:
|
||||
end = start + n
|
||||
assert all(vpos[start][0] == vpos[i][0] for i in range(start + 1, end))
|
||||
start += n
|
||||
|
||||
vpos = nx.multipartite_layout(G, align="horizontal", scale=2, center=(2, 2))
|
||||
assert len(vpos) == len(G)
|
||||
|
||||
start = 0
|
||||
for n in sizes:
|
||||
end = start + n
|
||||
assert all(vpos[start][1] == vpos[i][1] for i in range(start + 1, end))
|
||||
start += n
|
||||
|
||||
pytest.raises(ValueError, nx.multipartite_layout, G, align="foo")
|
||||
|
||||
def test_kamada_kawai_costfn_1d(self):
|
||||
costfn = nx.drawing.layout._kamada_kawai_costfn
|
||||
|
||||
pos = np.array([4.0, 7.0])
|
||||
invdist = 1 / np.array([[0.1, 2.0], [2.0, 0.3]])
|
||||
|
||||
cost, grad = costfn(pos, np, invdist, meanweight=0, dim=1)
|
||||
|
||||
assert cost == pytest.approx(((3 / 2.0 - 1) ** 2), abs=1e-7)
|
||||
assert grad[0] == pytest.approx((-0.5), abs=1e-7)
|
||||
assert grad[1] == pytest.approx(0.5, abs=1e-7)
|
||||
|
||||
def check_kamada_kawai_costfn(self, pos, invdist, meanwt, dim):
|
||||
costfn = nx.drawing.layout._kamada_kawai_costfn
|
||||
|
||||
cost, grad = costfn(pos.ravel(), np, invdist, meanweight=meanwt, dim=dim)
|
||||
|
||||
expected_cost = 0.5 * meanwt * np.sum(np.sum(pos, axis=0) ** 2)
|
||||
for i in range(pos.shape[0]):
|
||||
for j in range(i + 1, pos.shape[0]):
|
||||
diff = np.linalg.norm(pos[i] - pos[j])
|
||||
expected_cost += (diff * invdist[i][j] - 1.0) ** 2
|
||||
|
||||
assert cost == pytest.approx(expected_cost, abs=1e-7)
|
||||
|
||||
dx = 1e-4
|
||||
for nd in range(pos.shape[0]):
|
||||
for dm in range(pos.shape[1]):
|
||||
idx = nd * pos.shape[1] + dm
|
||||
ps = pos.flatten()
|
||||
|
||||
ps[idx] += dx
|
||||
cplus = costfn(ps, np, invdist, meanweight=meanwt, dim=pos.shape[1])[0]
|
||||
|
||||
ps[idx] -= 2 * dx
|
||||
cminus = costfn(ps, np, invdist, meanweight=meanwt, dim=pos.shape[1])[0]
|
||||
|
||||
assert grad[idx] == pytest.approx((cplus - cminus) / (2 * dx), abs=1e-5)
|
||||
|
||||
def test_kamada_kawai_costfn(self):
|
||||
invdist = 1 / np.array([[0.1, 2.1, 1.7], [2.1, 0.2, 0.6], [1.7, 0.6, 0.3]])
|
||||
meanwt = 0.3
|
||||
|
||||
# 2d
|
||||
pos = np.array([[1.3, -3.2], [2.7, -0.3], [5.1, 2.5]])
|
||||
|
||||
self.check_kamada_kawai_costfn(pos, invdist, meanwt, 2)
|
||||
|
||||
# 3d
|
||||
pos = np.array([[0.9, 8.6, -8.7], [-10, -0.5, -7.1], [9.1, -8.1, 1.6]])
|
||||
|
||||
self.check_kamada_kawai_costfn(pos, invdist, meanwt, 3)
|
||||
|
||||
def test_spiral_layout(self):
|
||||
G = self.Gs
|
||||
|
||||
# a lower value of resolution should result in a more compact layout
|
||||
# intuitively, the total distance from the start and end nodes
|
||||
# via each node in between (transiting through each) will be less,
|
||||
# assuming rescaling does not occur on the computed node positions
|
||||
pos_standard = np.array(list(nx.spiral_layout(G, resolution=0.35).values()))
|
||||
pos_tighter = np.array(list(nx.spiral_layout(G, resolution=0.34).values()))
|
||||
distances = np.linalg.norm(pos_standard[:-1] - pos_standard[1:], axis=1)
|
||||
distances_tighter = np.linalg.norm(pos_tighter[:-1] - pos_tighter[1:], axis=1)
|
||||
assert sum(distances) > sum(distances_tighter)
|
||||
|
||||
# return near-equidistant points after the first value if set to true
|
||||
pos_equidistant = np.array(list(nx.spiral_layout(G, equidistant=True).values()))
|
||||
distances_equidistant = np.linalg.norm(
|
||||
pos_equidistant[:-1] - pos_equidistant[1:], axis=1
|
||||
)
|
||||
assert np.allclose(
|
||||
distances_equidistant[1:], distances_equidistant[-1], atol=0.01
|
||||
)
|
||||
|
||||
def test_spiral_layout_equidistant(self):
|
||||
G = nx.path_graph(10)
|
||||
pos = nx.spiral_layout(G, equidistant=True)
|
||||
# Extract individual node positions as an array
|
||||
p = np.array(list(pos.values()))
|
||||
# Elementwise-distance between node positions
|
||||
dist = np.linalg.norm(p[1:] - p[:-1], axis=1)
|
||||
assert np.allclose(np.diff(dist), 0, atol=1e-3)
|
||||
|
||||
def test_rescale_layout_dict(self):
|
||||
G = nx.empty_graph()
|
||||
vpos = nx.random_layout(G, center=(1, 1))
|
||||
assert nx.rescale_layout_dict(vpos) == {}
|
||||
|
||||
G = nx.empty_graph(2)
|
||||
vpos = {0: (0.0, 0.0), 1: (1.0, 1.0)}
|
||||
s_vpos = nx.rescale_layout_dict(vpos)
|
||||
assert np.linalg.norm([sum(x) for x in zip(*s_vpos.values())]) < 1e-6
|
||||
|
||||
G = nx.empty_graph(3)
|
||||
vpos = {0: (0, 0), 1: (1, 1), 2: (0.5, 0.5)}
|
||||
s_vpos = nx.rescale_layout_dict(vpos)
|
||||
|
||||
expectation = {
|
||||
0: np.array((-1, -1)),
|
||||
1: np.array((1, 1)),
|
||||
2: np.array((0, 0)),
|
||||
}
|
||||
for k, v in expectation.items():
|
||||
assert (s_vpos[k] == v).all()
|
||||
s_vpos = nx.rescale_layout_dict(vpos, scale=2)
|
||||
expectation = {
|
||||
0: np.array((-2, -2)),
|
||||
1: np.array((2, 2)),
|
||||
2: np.array((0, 0)),
|
||||
}
|
||||
for k, v in expectation.items():
|
||||
assert (s_vpos[k] == v).all()
|
||||
|
||||
def test_arf_layout_partial_input_test(self):
|
||||
"""
|
||||
Checks whether partial pos input still returns a proper position.
|
||||
"""
|
||||
G = self.Gs
|
||||
node = nx.utils.arbitrary_element(G)
|
||||
pos = nx.circular_layout(G)
|
||||
del pos[node]
|
||||
pos = nx.arf_layout(G, pos=pos)
|
||||
assert len(pos) == len(G)
|
||||
|
||||
def test_arf_layout_negative_a_check(self):
|
||||
"""
|
||||
Checks input parameters correctly raises errors. For example, `a` should be larger than 1
|
||||
"""
|
||||
G = self.Gs
|
||||
pytest.raises(ValueError, nx.arf_layout, G=G, a=-1)
|
||||
|
||||
|
||||
def test_multipartite_layout_nonnumeric_partition_labels():
|
||||
"""See gh-5123."""
|
||||
G = nx.Graph()
|
||||
G.add_node(0, subset="s0")
|
||||
G.add_node(1, subset="s0")
|
||||
G.add_node(2, subset="s1")
|
||||
G.add_node(3, subset="s1")
|
||||
G.add_edges_from([(0, 2), (0, 3), (1, 2)])
|
||||
pos = nx.multipartite_layout(G)
|
||||
assert len(pos) == len(G)
|
||||
|
||||
|
||||
def test_multipartite_layout_layer_order():
|
||||
"""Return the layers in sorted order if the layers of the multipartite
|
||||
graph are sortable. See gh-5691"""
|
||||
G = nx.Graph()
|
||||
for node, layer in zip(("a", "b", "c", "d", "e"), (2, 3, 1, 2, 4)):
|
||||
G.add_node(node, subset=layer)
|
||||
|
||||
# Horizontal alignment, therefore y-coord determines layers
|
||||
pos = nx.multipartite_layout(G, align="horizontal")
|
||||
|
||||
# Nodes "a" and "d" are in the same layer
|
||||
assert pos["a"][-1] == pos["d"][-1]
|
||||
# positions should be sorted according to layer
|
||||
assert pos["c"][-1] < pos["a"][-1] < pos["b"][-1] < pos["e"][-1]
|
||||
|
||||
# Make sure that multipartite_layout still works when layers are not sortable
|
||||
G.nodes["a"]["subset"] = "layer_0" # Can't sort mixed strs/ints
|
||||
pos_nosort = nx.multipartite_layout(G) # smoke test: this should not raise
|
||||
assert pos_nosort.keys() == pos.keys()
|
||||
190
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_pydot.py
vendored
Normal file
190
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_pydot.py
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
"""Unit tests for pydot drawing functions."""
|
||||
import os
|
||||
import tempfile
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
import networkx as nx
|
||||
from networkx.utils import graphs_equal
|
||||
|
||||
pydot = pytest.importorskip("pydot")
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
class TestPydot:
|
||||
def pydot_checks(self, G, prog):
|
||||
"""
|
||||
Validate :mod:`pydot`-based usage of the passed NetworkX graph with the
|
||||
passed basename of an external GraphViz command (e.g., `dot`, `neato`).
|
||||
"""
|
||||
|
||||
# Set the name of this graph to... "G". Failing to do so will
|
||||
# subsequently trip an assertion expecting this name.
|
||||
G.graph["name"] = "G"
|
||||
|
||||
# Add arbitrary nodes and edges to the passed empty graph.
|
||||
G.add_edges_from([("A", "B"), ("A", "C"), ("B", "C"), ("A", "D")])
|
||||
G.add_node("E")
|
||||
|
||||
# Validate layout of this graph with the passed GraphViz command.
|
||||
graph_layout = nx.nx_pydot.pydot_layout(G, prog=prog)
|
||||
assert isinstance(graph_layout, dict)
|
||||
|
||||
# Convert this graph into a "pydot.Dot" instance.
|
||||
P = nx.nx_pydot.to_pydot(G)
|
||||
|
||||
# Convert this "pydot.Dot" instance back into a graph of the same type.
|
||||
G2 = G.__class__(nx.nx_pydot.from_pydot(P))
|
||||
|
||||
# Validate the original and resulting graphs to be the same.
|
||||
assert graphs_equal(G, G2)
|
||||
|
||||
fd, fname = tempfile.mkstemp()
|
||||
|
||||
# Serialize this "pydot.Dot" instance to a temporary file in dot format
|
||||
P.write_raw(fname)
|
||||
|
||||
# Deserialize a list of new "pydot.Dot" instances back from this file.
|
||||
Pin_list = pydot.graph_from_dot_file(path=fname, encoding="utf-8")
|
||||
|
||||
# Validate this file to contain only one graph.
|
||||
assert len(Pin_list) == 1
|
||||
|
||||
# The single "pydot.Dot" instance deserialized from this file.
|
||||
Pin = Pin_list[0]
|
||||
|
||||
# Sorted list of all nodes in the original "pydot.Dot" instance.
|
||||
n1 = sorted(p.get_name() for p in P.get_node_list())
|
||||
|
||||
# Sorted list of all nodes in the deserialized "pydot.Dot" instance.
|
||||
n2 = sorted(p.get_name() for p in Pin.get_node_list())
|
||||
|
||||
# Validate these instances to contain the same nodes.
|
||||
assert n1 == n2
|
||||
|
||||
# Sorted list of all edges in the original "pydot.Dot" instance.
|
||||
e1 = sorted((e.get_source(), e.get_destination()) for e in P.get_edge_list())
|
||||
|
||||
# Sorted list of all edges in the original "pydot.Dot" instance.
|
||||
e2 = sorted((e.get_source(), e.get_destination()) for e in Pin.get_edge_list())
|
||||
|
||||
# Validate these instances to contain the same edges.
|
||||
assert e1 == e2
|
||||
|
||||
# Deserialize a new graph of the same type back from this file.
|
||||
Hin = nx.nx_pydot.read_dot(fname)
|
||||
Hin = G.__class__(Hin)
|
||||
|
||||
# Validate the original and resulting graphs to be the same.
|
||||
assert graphs_equal(G, Hin)
|
||||
|
||||
os.close(fd)
|
||||
os.unlink(fname)
|
||||
|
||||
def test_undirected(self):
|
||||
self.pydot_checks(nx.Graph(), prog="neato")
|
||||
|
||||
def test_directed(self):
|
||||
self.pydot_checks(nx.DiGraph(), prog="dot")
|
||||
|
||||
def test_read_write(self):
|
||||
G = nx.MultiGraph()
|
||||
G.graph["name"] = "G"
|
||||
G.add_edge("1", "2", key="0") # read assumes strings
|
||||
fh = StringIO()
|
||||
nx.nx_pydot.write_dot(G, fh)
|
||||
fh.seek(0)
|
||||
H = nx.nx_pydot.read_dot(fh)
|
||||
assert graphs_equal(G, H)
|
||||
|
||||
|
||||
def test_pydot_issue_258():
|
||||
G = nx.Graph([("Example:A", 1)])
|
||||
with pytest.raises(ValueError):
|
||||
nx.nx_pydot.to_pydot(G)
|
||||
with pytest.raises(ValueError):
|
||||
nx.nx_pydot.pydot_layout(G)
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_node("1.2", style="filled", fillcolor="red:yellow")
|
||||
with pytest.raises(ValueError):
|
||||
nx.nx_pydot.to_pydot(G)
|
||||
G.remove_node("1.2")
|
||||
G.add_node("1.2", style="filled", fillcolor='"red:yellow"')
|
||||
assert (
|
||||
G.nodes.data() == nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).nodes.data()
|
||||
)
|
||||
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("1", "2", foo="bar:1")
|
||||
with pytest.raises(ValueError):
|
||||
nx.nx_pydot.to_pydot(G)
|
||||
G = nx.DiGraph()
|
||||
G.add_edge("1", "2", foo='"bar:1"')
|
||||
assert G["1"]["2"] == nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G))["1"]["2"]
|
||||
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge("1", "2", foo="b:1")
|
||||
G.add_edge("1", "2", bar="foo:foo")
|
||||
with pytest.raises(ValueError):
|
||||
nx.nx_pydot.to_pydot(G)
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge("1", "2", foo='"b:1"')
|
||||
G.add_edge("1", "2", bar='"foo:foo"')
|
||||
# Keys as integers aren't preserved in the conversion. They are read as strings.
|
||||
assert [attr for _, _, attr in G.edges.data()] == [
|
||||
attr
|
||||
for _, _, attr in nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).edges.data()
|
||||
]
|
||||
|
||||
G = nx.Graph()
|
||||
G.add_edge("1", "2")
|
||||
G["1"]["2"]["f:oo"] = "bar"
|
||||
with pytest.raises(ValueError):
|
||||
nx.nx_pydot.to_pydot(G)
|
||||
G = nx.Graph()
|
||||
G.add_edge("1", "2")
|
||||
G["1"]["2"]['"f:oo"'] = "bar"
|
||||
assert G["1"]["2"] == nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G))["1"]["2"]
|
||||
|
||||
G = nx.Graph([('"Example:A"', 1)])
|
||||
layout = nx.nx_pydot.pydot_layout(G)
|
||||
assert isinstance(layout, dict)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"graph_type", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph]
|
||||
)
|
||||
def test_hashable_pydot(graph_type):
|
||||
# gh-5790
|
||||
G = graph_type()
|
||||
G.add_edge("5", frozenset([1]), t='"Example:A"', l=False)
|
||||
G.add_edge("1", 2, w=True, t=("node1",), l=frozenset(["node1"]))
|
||||
G.add_edge("node", (3, 3), w="string")
|
||||
|
||||
assert [
|
||||
{"t": '"Example:A"', "l": "False"},
|
||||
{"w": "True", "t": "('node1',)", "l": "frozenset({'node1'})"},
|
||||
{"w": "string"},
|
||||
] == [
|
||||
attr
|
||||
for _, _, attr in nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).edges.data()
|
||||
]
|
||||
|
||||
assert {str(i) for i in G.nodes()} == set(
|
||||
nx.nx_pydot.from_pydot(nx.nx_pydot.to_pydot(G)).nodes
|
||||
)
|
||||
|
||||
|
||||
def test_pydot_numrical_name():
|
||||
G = nx.Graph()
|
||||
G.add_edges_from([("A", "B"), (0, 1)])
|
||||
graph_layout = nx.nx_pydot.pydot_layout(G, prog="dot")
|
||||
assert isinstance(graph_layout, dict)
|
||||
assert "0" not in graph_layout
|
||||
assert 0 in graph_layout
|
||||
assert "1" not in graph_layout
|
||||
assert 1 in graph_layout
|
||||
assert "A" in graph_layout
|
||||
assert "B" in graph_layout
|
||||
791
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_pylab.py
vendored
Normal file
791
.CondaPkg/env/Lib/site-packages/networkx/drawing/tests/test_pylab.py
vendored
Normal file
@@ -0,0 +1,791 @@
|
||||
"""Unit tests for matplotlib drawing functions."""
|
||||
import itertools
|
||||
import os
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
mpl = pytest.importorskip("matplotlib")
|
||||
np = pytest.importorskip("numpy")
|
||||
mpl.use("PS")
|
||||
plt = pytest.importorskip("matplotlib.pyplot")
|
||||
plt.rcParams["text.usetex"] = False
|
||||
|
||||
|
||||
import networkx as nx
|
||||
|
||||
barbell = nx.barbell_graph(4, 6)
|
||||
|
||||
|
||||
def test_draw():
|
||||
try:
|
||||
functions = [
|
||||
nx.draw_circular,
|
||||
nx.draw_kamada_kawai,
|
||||
nx.draw_planar,
|
||||
nx.draw_random,
|
||||
nx.draw_spectral,
|
||||
nx.draw_spring,
|
||||
nx.draw_shell,
|
||||
]
|
||||
options = [{"node_color": "black", "node_size": 100, "width": 3}]
|
||||
for function, option in itertools.product(functions, options):
|
||||
function(barbell, **option)
|
||||
plt.savefig("test.ps")
|
||||
|
||||
finally:
|
||||
try:
|
||||
os.unlink("test.ps")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_draw_shell_nlist():
|
||||
try:
|
||||
nlist = [list(range(4)), list(range(4, 10)), list(range(10, 14))]
|
||||
nx.draw_shell(barbell, nlist=nlist)
|
||||
plt.savefig("test.ps")
|
||||
finally:
|
||||
try:
|
||||
os.unlink("test.ps")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def test_edge_colormap():
|
||||
colors = range(barbell.number_of_edges())
|
||||
nx.draw_spring(
|
||||
barbell, edge_color=colors, width=4, edge_cmap=plt.cm.Blues, with_labels=True
|
||||
)
|
||||
# plt.show()
|
||||
|
||||
|
||||
def test_arrows():
|
||||
nx.draw_spring(barbell.to_directed())
|
||||
# plt.show()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("edge_color", "expected"),
|
||||
(
|
||||
(None, "black"), # Default
|
||||
("r", "red"), # Non-default color string
|
||||
(["r"], "red"), # Single non-default color in a list
|
||||
((1.0, 1.0, 0.0), "yellow"), # single color as rgb tuple
|
||||
([(1.0, 1.0, 0.0)], "yellow"), # single color as rgb tuple in list
|
||||
((0, 1, 0, 1), "lime"), # single color as rgba tuple
|
||||
([(0, 1, 0, 1)], "lime"), # single color as rgba tuple in list
|
||||
("#0000ff", "blue"), # single color hex code
|
||||
(["#0000ff"], "blue"), # hex code in list
|
||||
),
|
||||
)
|
||||
@pytest.mark.parametrize("edgelist", (None, [(0, 1)]))
|
||||
def test_single_edge_color_undirected(edge_color, expected, edgelist):
|
||||
"""Tests ways of specifying all edges have a single color for edges
|
||||
drawn with a LineCollection"""
|
||||
|
||||
G = nx.path_graph(3)
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos=nx.random_layout(G), edgelist=edgelist, edge_color=edge_color
|
||||
)
|
||||
assert mpl.colors.same_color(drawn_edges.get_color(), expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("edge_color", "expected"),
|
||||
(
|
||||
(None, "black"), # Default
|
||||
("r", "red"), # Non-default color string
|
||||
(["r"], "red"), # Single non-default color in a list
|
||||
((1.0, 1.0, 0.0), "yellow"), # single color as rgb tuple
|
||||
([(1.0, 1.0, 0.0)], "yellow"), # single color as rgb tuple in list
|
||||
((0, 1, 0, 1), "lime"), # single color as rgba tuple
|
||||
([(0, 1, 0, 1)], "lime"), # single color as rgba tuple in list
|
||||
("#0000ff", "blue"), # single color hex code
|
||||
(["#0000ff"], "blue"), # hex code in list
|
||||
),
|
||||
)
|
||||
@pytest.mark.parametrize("edgelist", (None, [(0, 1)]))
|
||||
def test_single_edge_color_directed(edge_color, expected, edgelist):
|
||||
"""Tests ways of specifying all edges have a single color for edges drawn
|
||||
with FancyArrowPatches"""
|
||||
|
||||
G = nx.path_graph(3, create_using=nx.DiGraph)
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos=nx.random_layout(G), edgelist=edgelist, edge_color=edge_color
|
||||
)
|
||||
for fap in drawn_edges:
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), expected)
|
||||
|
||||
|
||||
def test_edge_color_tuple_interpretation():
|
||||
"""If edge_color is a sequence with the same length as edgelist, then each
|
||||
value in edge_color is mapped onto each edge via colormap."""
|
||||
G = nx.path_graph(6, create_using=nx.DiGraph)
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
|
||||
# num edges != 3 or 4 --> edge_color interpreted as rgb(a)
|
||||
for ec in ((0, 0, 1), (0, 0, 1, 1)):
|
||||
# More than 4 edges
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=ec)
|
||||
for fap in drawn_edges:
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), ec)
|
||||
# Fewer than 3 edges
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos, edgelist=[(0, 1), (1, 2)], edge_color=ec
|
||||
)
|
||||
for fap in drawn_edges:
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), ec)
|
||||
|
||||
# num edges == 3, len(edge_color) == 4: interpreted as rgba
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos, edgelist=[(0, 1), (1, 2), (2, 3)], edge_color=(0, 0, 1, 1)
|
||||
)
|
||||
for fap in drawn_edges:
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), "blue")
|
||||
|
||||
# num edges == 4, len(edge_color) == 3: interpreted as rgb
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos, edgelist=[(0, 1), (1, 2), (2, 3), (3, 4)], edge_color=(0, 0, 1)
|
||||
)
|
||||
for fap in drawn_edges:
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), "blue")
|
||||
|
||||
# num edges == len(edge_color) == 3: interpreted with cmap, *not* as rgb
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos, edgelist=[(0, 1), (1, 2), (2, 3)], edge_color=(0, 0, 1)
|
||||
)
|
||||
assert mpl.colors.same_color(
|
||||
drawn_edges[0].get_edgecolor(), drawn_edges[1].get_edgecolor()
|
||||
)
|
||||
for fap in drawn_edges:
|
||||
assert not mpl.colors.same_color(fap.get_edgecolor(), "blue")
|
||||
|
||||
# num edges == len(edge_color) == 4: interpreted with cmap, *not* as rgba
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos, edgelist=[(0, 1), (1, 2), (2, 3), (3, 4)], edge_color=(0, 0, 1, 1)
|
||||
)
|
||||
assert mpl.colors.same_color(
|
||||
drawn_edges[0].get_edgecolor(), drawn_edges[1].get_edgecolor()
|
||||
)
|
||||
assert mpl.colors.same_color(
|
||||
drawn_edges[2].get_edgecolor(), drawn_edges[3].get_edgecolor()
|
||||
)
|
||||
for fap in drawn_edges:
|
||||
assert not mpl.colors.same_color(fap.get_edgecolor(), "blue")
|
||||
|
||||
|
||||
def test_fewer_edge_colors_than_num_edges_directed():
|
||||
"""Test that the edge colors are cycled when there are fewer specified
|
||||
colors than edges."""
|
||||
G = barbell.to_directed()
|
||||
pos = nx.random_layout(barbell)
|
||||
edgecolors = ("r", "g", "b")
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=edgecolors)
|
||||
for fap, expected in zip(drawn_edges, itertools.cycle(edgecolors)):
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), expected)
|
||||
|
||||
|
||||
def test_more_edge_colors_than_num_edges_directed():
|
||||
"""Test that extra edge colors are ignored when there are more specified
|
||||
colors than edges."""
|
||||
G = nx.path_graph(4, create_using=nx.DiGraph) # 3 edges
|
||||
pos = nx.random_layout(barbell)
|
||||
edgecolors = ("r", "g", "b", "c") # 4 edge colors
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=edgecolors)
|
||||
for fap, expected in zip(drawn_edges, edgecolors[:-1]):
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), expected)
|
||||
|
||||
|
||||
def test_edge_color_string_with_global_alpha_undirected():
|
||||
edge_collection = nx.draw_networkx_edges(
|
||||
barbell,
|
||||
pos=nx.random_layout(barbell),
|
||||
edgelist=[(0, 1), (1, 2)],
|
||||
edge_color="purple",
|
||||
alpha=0.2,
|
||||
)
|
||||
ec = edge_collection.get_color().squeeze() # as rgba tuple
|
||||
assert len(edge_collection.get_paths()) == 2
|
||||
assert mpl.colors.same_color(ec[:-1], "purple")
|
||||
assert ec[-1] == 0.2
|
||||
|
||||
|
||||
def test_edge_color_string_with_global_alpha_directed():
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
barbell.to_directed(),
|
||||
pos=nx.random_layout(barbell),
|
||||
edgelist=[(0, 1), (1, 2)],
|
||||
edge_color="purple",
|
||||
alpha=0.2,
|
||||
)
|
||||
assert len(drawn_edges) == 2
|
||||
for fap in drawn_edges:
|
||||
ec = fap.get_edgecolor() # As rgba tuple
|
||||
assert mpl.colors.same_color(ec[:-1], "purple")
|
||||
assert ec[-1] == 0.2
|
||||
|
||||
|
||||
@pytest.mark.parametrize("graph_type", (nx.Graph, nx.DiGraph))
|
||||
def test_edge_width_default_value(graph_type):
|
||||
"""Test the default linewidth for edges drawn either via LineCollection or
|
||||
FancyArrowPatches."""
|
||||
G = nx.path_graph(2, create_using=graph_type)
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos)
|
||||
if isinstance(drawn_edges, list): # directed case: list of FancyArrowPatch
|
||||
drawn_edges = drawn_edges[0]
|
||||
assert drawn_edges.get_linewidth() == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("edgewidth", "expected"),
|
||||
(
|
||||
(3, 3), # single-value, non-default
|
||||
([3], 3), # Single value as a list
|
||||
),
|
||||
)
|
||||
def test_edge_width_single_value_undirected(edgewidth, expected):
|
||||
G = nx.path_graph(4)
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, width=edgewidth)
|
||||
assert len(drawn_edges.get_paths()) == 3
|
||||
assert drawn_edges.get_linewidth() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("edgewidth", "expected"),
|
||||
(
|
||||
(3, 3), # single-value, non-default
|
||||
([3], 3), # Single value as a list
|
||||
),
|
||||
)
|
||||
def test_edge_width_single_value_directed(edgewidth, expected):
|
||||
G = nx.path_graph(4, create_using=nx.DiGraph)
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, width=edgewidth)
|
||||
assert len(drawn_edges) == 3
|
||||
for fap in drawn_edges:
|
||||
assert fap.get_linewidth() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"edgelist",
|
||||
(
|
||||
[(0, 1), (1, 2), (2, 3)], # one width specification per edge
|
||||
None, # fewer widths than edges - widths cycle
|
||||
[(0, 1), (1, 2)], # More widths than edges - unused widths ignored
|
||||
),
|
||||
)
|
||||
def test_edge_width_sequence(edgelist):
|
||||
G = barbell.to_directed()
|
||||
pos = nx.random_layout(G)
|
||||
widths = (0.5, 2.0, 12.0)
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, edgelist=edgelist, width=widths)
|
||||
for fap, expected_width in zip(drawn_edges, itertools.cycle(widths)):
|
||||
assert fap.get_linewidth() == expected_width
|
||||
|
||||
|
||||
def test_edge_color_with_edge_vmin_vmax():
|
||||
"""Test that edge_vmin and edge_vmax properly set the dynamic range of the
|
||||
color map when num edges == len(edge_colors)."""
|
||||
G = nx.path_graph(3, create_using=nx.DiGraph)
|
||||
pos = nx.random_layout(G)
|
||||
# Extract colors from the original (unscaled) colormap
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, edge_color=[0, 1.0])
|
||||
orig_colors = [e.get_edgecolor() for e in drawn_edges]
|
||||
# Colors from scaled colormap
|
||||
drawn_edges = nx.draw_networkx_edges(
|
||||
G, pos, edge_color=[0.2, 0.8], edge_vmin=0.2, edge_vmax=0.8
|
||||
)
|
||||
scaled_colors = [e.get_edgecolor() for e in drawn_edges]
|
||||
assert mpl.colors.same_color(orig_colors, scaled_colors)
|
||||
|
||||
|
||||
def test_directed_edges_linestyle_default():
|
||||
"""Test default linestyle for edges drawn with FancyArrowPatches."""
|
||||
G = nx.path_graph(4, create_using=nx.DiGraph) # Graph with 3 edges
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
|
||||
# edge with default style
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos)
|
||||
assert len(drawn_edges) == 3
|
||||
for fap in drawn_edges:
|
||||
assert fap.get_linestyle() == "solid"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"style",
|
||||
(
|
||||
"dashed", # edge with string style
|
||||
"--", # edge with simplified string style
|
||||
(1, (1, 1)), # edge with (offset, onoffseq) style
|
||||
),
|
||||
)
|
||||
def test_directed_edges_linestyle_single_value(style):
|
||||
"""Tests support for specifying linestyles with a single value to be applied to
|
||||
all edges in ``draw_networkx_edges`` for FancyArrowPatch outputs
|
||||
(e.g. directed edges)."""
|
||||
|
||||
G = nx.path_graph(4, create_using=nx.DiGraph) # Graph with 3 edges
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, style=style)
|
||||
assert len(drawn_edges) == 3
|
||||
for fap in drawn_edges:
|
||||
assert fap.get_linestyle() == style
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"style_seq",
|
||||
(
|
||||
["dashed"], # edge with string style in list
|
||||
["--"], # edge with simplified string style in list
|
||||
[(1, (1, 1))], # edge with (offset, onoffseq) style in list
|
||||
["--", "-", ":"], # edges with styles for each edge
|
||||
["--", "-"], # edges with fewer styles than edges (styles cycle)
|
||||
["--", "-", ":", "-."], # edges with more styles than edges (extra unused)
|
||||
),
|
||||
)
|
||||
def test_directed_edges_linestyle_sequence(style_seq):
|
||||
"""Tests support for specifying linestyles with sequences in
|
||||
``draw_networkx_edges`` for FancyArrowPatch outputs (e.g. directed edges)."""
|
||||
|
||||
G = nx.path_graph(4, create_using=nx.DiGraph) # Graph with 3 edges
|
||||
pos = {n: (n, n) for n in range(len(G))}
|
||||
|
||||
drawn_edges = nx.draw_networkx_edges(G, pos, style=style_seq)
|
||||
assert len(drawn_edges) == 3
|
||||
for fap, style in zip(drawn_edges, itertools.cycle(style_seq)):
|
||||
assert fap.get_linestyle() == style
|
||||
|
||||
|
||||
def test_labels_and_colors():
|
||||
G = nx.cubical_graph()
|
||||
pos = nx.spring_layout(G) # positions for all nodes
|
||||
# nodes
|
||||
nx.draw_networkx_nodes(
|
||||
G, pos, nodelist=[0, 1, 2, 3], node_color="r", node_size=500, alpha=0.75
|
||||
)
|
||||
nx.draw_networkx_nodes(
|
||||
G,
|
||||
pos,
|
||||
nodelist=[4, 5, 6, 7],
|
||||
node_color="b",
|
||||
node_size=500,
|
||||
alpha=[0.25, 0.5, 0.75, 1.0],
|
||||
)
|
||||
# edges
|
||||
nx.draw_networkx_edges(G, pos, width=1.0, alpha=0.5)
|
||||
nx.draw_networkx_edges(
|
||||
G,
|
||||
pos,
|
||||
edgelist=[(0, 1), (1, 2), (2, 3), (3, 0)],
|
||||
width=8,
|
||||
alpha=0.5,
|
||||
edge_color="r",
|
||||
)
|
||||
nx.draw_networkx_edges(
|
||||
G,
|
||||
pos,
|
||||
edgelist=[(4, 5), (5, 6), (6, 7), (7, 4)],
|
||||
width=8,
|
||||
alpha=0.5,
|
||||
edge_color="b",
|
||||
)
|
||||
nx.draw_networkx_edges(
|
||||
G,
|
||||
pos,
|
||||
edgelist=[(4, 5), (5, 6), (6, 7), (7, 4)],
|
||||
arrows=True,
|
||||
min_source_margin=0.5,
|
||||
min_target_margin=0.75,
|
||||
width=8,
|
||||
edge_color="b",
|
||||
)
|
||||
# some math labels
|
||||
labels = {}
|
||||
labels[0] = r"$a$"
|
||||
labels[1] = r"$b$"
|
||||
labels[2] = r"$c$"
|
||||
labels[3] = r"$d$"
|
||||
labels[4] = r"$\alpha$"
|
||||
labels[5] = r"$\beta$"
|
||||
labels[6] = r"$\gamma$"
|
||||
labels[7] = r"$\delta$"
|
||||
nx.draw_networkx_labels(G, pos, labels, font_size=16)
|
||||
nx.draw_networkx_edge_labels(G, pos, edge_labels=None, rotate=False)
|
||||
nx.draw_networkx_edge_labels(G, pos, edge_labels={(4, 5): "4-5"})
|
||||
# plt.show()
|
||||
|
||||
|
||||
@pytest.mark.mpl_image_compare
|
||||
def test_house_with_colors():
|
||||
G = nx.house_graph()
|
||||
# explicitly set positions
|
||||
fig, ax = plt.subplots()
|
||||
pos = {0: (0, 0), 1: (1, 0), 2: (0, 1), 3: (1, 1), 4: (0.5, 2.0)}
|
||||
|
||||
# Plot nodes with different properties for the "wall" and "roof" nodes
|
||||
nx.draw_networkx_nodes(
|
||||
G,
|
||||
pos,
|
||||
node_size=3000,
|
||||
nodelist=[0, 1, 2, 3],
|
||||
node_color="tab:blue",
|
||||
)
|
||||
nx.draw_networkx_nodes(
|
||||
G, pos, node_size=2000, nodelist=[4], node_color="tab:orange"
|
||||
)
|
||||
nx.draw_networkx_edges(G, pos, alpha=0.5, width=6)
|
||||
# Customize axes
|
||||
ax.margins(0.11)
|
||||
plt.tight_layout()
|
||||
plt.axis("off")
|
||||
return fig
|
||||
|
||||
|
||||
def test_axes():
|
||||
fig, ax = plt.subplots()
|
||||
nx.draw(barbell, ax=ax)
|
||||
nx.draw_networkx_edge_labels(barbell, nx.circular_layout(barbell), ax=ax)
|
||||
|
||||
|
||||
def test_empty_graph():
|
||||
G = nx.Graph()
|
||||
nx.draw(G)
|
||||
|
||||
|
||||
def test_draw_empty_nodes_return_values():
|
||||
# See Issue #3833
|
||||
import matplotlib.collections # call as mpl.collections
|
||||
|
||||
G = nx.Graph([(1, 2), (2, 3)])
|
||||
DG = nx.DiGraph([(1, 2), (2, 3)])
|
||||
pos = nx.circular_layout(G)
|
||||
assert isinstance(
|
||||
nx.draw_networkx_nodes(G, pos, nodelist=[]), mpl.collections.PathCollection
|
||||
)
|
||||
assert isinstance(
|
||||
nx.draw_networkx_nodes(DG, pos, nodelist=[]), mpl.collections.PathCollection
|
||||
)
|
||||
|
||||
# drawing empty edges used to return an empty LineCollection or empty list.
|
||||
# Now it is always an empty list (because edges are now lists of FancyArrows)
|
||||
assert nx.draw_networkx_edges(G, pos, edgelist=[], arrows=True) == []
|
||||
assert nx.draw_networkx_edges(G, pos, edgelist=[], arrows=False) == []
|
||||
assert nx.draw_networkx_edges(DG, pos, edgelist=[], arrows=False) == []
|
||||
assert nx.draw_networkx_edges(DG, pos, edgelist=[], arrows=True) == []
|
||||
|
||||
|
||||
def test_multigraph_edgelist_tuples():
|
||||
# See Issue #3295
|
||||
G = nx.path_graph(3, create_using=nx.MultiDiGraph)
|
||||
nx.draw_networkx(G, edgelist=[(0, 1, 0)])
|
||||
nx.draw_networkx(G, edgelist=[(0, 1, 0)], node_size=[10, 20, 0])
|
||||
|
||||
|
||||
def test_alpha_iter():
|
||||
pos = nx.random_layout(barbell)
|
||||
fig = plt.figure()
|
||||
# with fewer alpha elements than nodes
|
||||
fig.add_subplot(131) # Each test in a new axis object
|
||||
nx.draw_networkx_nodes(barbell, pos, alpha=[0.1, 0.2])
|
||||
# with equal alpha elements and nodes
|
||||
num_nodes = len(barbell.nodes)
|
||||
alpha = [x / num_nodes for x in range(num_nodes)]
|
||||
colors = range(num_nodes)
|
||||
fig.add_subplot(132)
|
||||
nx.draw_networkx_nodes(barbell, pos, node_color=colors, alpha=alpha)
|
||||
# with more alpha elements than nodes
|
||||
alpha.append(1)
|
||||
fig.add_subplot(133)
|
||||
nx.draw_networkx_nodes(barbell, pos, alpha=alpha)
|
||||
|
||||
|
||||
def test_error_invalid_kwds():
|
||||
with pytest.raises(ValueError, match="Received invalid argument"):
|
||||
nx.draw(barbell, foo="bar")
|
||||
|
||||
|
||||
def test_draw_networkx_arrowsize_incorrect_size():
|
||||
G = nx.DiGraph([(0, 1), (0, 2), (0, 3), (1, 3)])
|
||||
arrowsize = [1, 2, 3]
|
||||
with pytest.raises(
|
||||
ValueError, match="arrowsize should have the same length as edgelist"
|
||||
):
|
||||
nx.draw(G, arrowsize=arrowsize)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arrowsize", (30, [10, 20, 30]))
|
||||
def test_draw_edges_arrowsize(arrowsize):
|
||||
G = nx.DiGraph([(0, 1), (0, 2), (1, 2)])
|
||||
pos = {0: (0, 0), 1: (0, 1), 2: (1, 0)}
|
||||
edges = nx.draw_networkx_edges(G, pos=pos, arrowsize=arrowsize)
|
||||
|
||||
arrowsize = itertools.repeat(arrowsize) if isinstance(arrowsize, int) else arrowsize
|
||||
|
||||
for fap, expected in zip(edges, arrowsize):
|
||||
assert isinstance(fap, mpl.patches.FancyArrowPatch)
|
||||
assert fap.get_mutation_scale() == expected
|
||||
|
||||
|
||||
def test_np_edgelist():
|
||||
# see issue #4129
|
||||
nx.draw_networkx(barbell, edgelist=np.array([(0, 2), (0, 3)]))
|
||||
|
||||
|
||||
def test_draw_nodes_missing_node_from_position():
|
||||
G = nx.path_graph(3)
|
||||
pos = {0: (0, 0), 1: (1, 1)} # No position for node 2
|
||||
with pytest.raises(nx.NetworkXError, match="has no position"):
|
||||
nx.draw_networkx_nodes(G, pos)
|
||||
|
||||
|
||||
# NOTE: parametrizing on marker to test both branches of internal
|
||||
# nx.draw_networkx_edges.to_marker_edge function
|
||||
@pytest.mark.parametrize("node_shape", ("o", "s"))
|
||||
def test_draw_edges_min_source_target_margins(node_shape):
|
||||
"""Test that there is a wider gap between the node and the start of an
|
||||
incident edge when min_source_margin is specified.
|
||||
|
||||
This test checks that the use of min_{source/target}_margin kwargs result
|
||||
in shorter (more padding) between the edges and source and target nodes.
|
||||
As a crude visual example, let 's' and 't' represent source and target
|
||||
nodes, respectively:
|
||||
|
||||
Default:
|
||||
s-----------------------------t
|
||||
|
||||
With margins:
|
||||
s ----------------------- t
|
||||
|
||||
"""
|
||||
# Create a single axis object to get consistent pixel coords across
|
||||
# multiple draws
|
||||
fig, ax = plt.subplots()
|
||||
G = nx.DiGraph([(0, 1)])
|
||||
pos = {0: (0, 0), 1: (1, 0)} # horizontal layout
|
||||
# Get leftmost and rightmost points of the FancyArrowPatch object
|
||||
# representing the edge between nodes 0 and 1 (in pixel coordinates)
|
||||
default_patch = nx.draw_networkx_edges(G, pos, ax=ax, node_shape=node_shape)[0]
|
||||
default_extent = default_patch.get_extents().corners()[::2, 0]
|
||||
# Now, do the same but with "padding" for the source and target via the
|
||||
# min_{source/target}_margin kwargs
|
||||
padded_patch = nx.draw_networkx_edges(
|
||||
G,
|
||||
pos,
|
||||
ax=ax,
|
||||
node_shape=node_shape,
|
||||
min_source_margin=100,
|
||||
min_target_margin=100,
|
||||
)[0]
|
||||
padded_extent = padded_patch.get_extents().corners()[::2, 0]
|
||||
|
||||
# With padding, the left-most extent of the edge should be further to the
|
||||
# right
|
||||
assert padded_extent[0] > default_extent[0]
|
||||
# And the rightmost extent of the edge, further to the left
|
||||
assert padded_extent[1] < default_extent[1]
|
||||
|
||||
|
||||
def test_nonzero_selfloop_with_single_node():
|
||||
"""Ensure that selfloop extent is non-zero when there is only one node."""
|
||||
# Create explicit axis object for test
|
||||
fig, ax = plt.subplots()
|
||||
# Graph with single node + self loop
|
||||
G = nx.DiGraph()
|
||||
G.add_node(0)
|
||||
G.add_edge(0, 0)
|
||||
# Draw
|
||||
patch = nx.draw_networkx_edges(G, {0: (0, 0)})[0]
|
||||
# The resulting patch must have non-zero extent
|
||||
bbox = patch.get_extents()
|
||||
assert bbox.width > 0 and bbox.height > 0
|
||||
# Cleanup
|
||||
plt.delaxes(ax)
|
||||
|
||||
|
||||
def test_nonzero_selfloop_with_single_edge_in_edgelist():
|
||||
"""Ensure that selfloop extent is non-zero when only a single edge is
|
||||
specified in the edgelist.
|
||||
"""
|
||||
# Create explicit axis object for test
|
||||
fig, ax = plt.subplots()
|
||||
# Graph with selfloop
|
||||
G = nx.path_graph(2, create_using=nx.DiGraph)
|
||||
G.add_edge(1, 1)
|
||||
pos = {n: (n, n) for n in G.nodes}
|
||||
# Draw only the selfloop edge via the `edgelist` kwarg
|
||||
patch = nx.draw_networkx_edges(G, pos, edgelist=[(1, 1)])[0]
|
||||
# The resulting patch must have non-zero extent
|
||||
bbox = patch.get_extents()
|
||||
assert bbox.width > 0 and bbox.height > 0
|
||||
# Cleanup
|
||||
plt.delaxes(ax)
|
||||
|
||||
|
||||
def test_apply_alpha():
|
||||
"""Test apply_alpha when there is a mismatch between the number of
|
||||
supplied colors and elements.
|
||||
"""
|
||||
nodelist = [0, 1, 2]
|
||||
colorlist = ["r", "g", "b"]
|
||||
alpha = 0.5
|
||||
rgba_colors = nx.drawing.nx_pylab.apply_alpha(colorlist, alpha, nodelist)
|
||||
assert all(rgba_colors[:, -1] == alpha)
|
||||
|
||||
|
||||
def test_draw_edges_toggling_with_arrows_kwarg():
|
||||
"""
|
||||
The `arrows` keyword argument is used as a 3-way switch to select which
|
||||
type of object to use for drawing edges:
|
||||
- ``arrows=None`` -> default (FancyArrowPatches for directed, else LineCollection)
|
||||
- ``arrows=True`` -> FancyArrowPatches
|
||||
- ``arrows=False`` -> LineCollection
|
||||
"""
|
||||
import matplotlib.collections
|
||||
import matplotlib.patches
|
||||
|
||||
UG = nx.path_graph(3)
|
||||
DG = nx.path_graph(3, create_using=nx.DiGraph)
|
||||
pos = {n: (n, n) for n in UG}
|
||||
|
||||
# Use FancyArrowPatches when arrows=True, regardless of graph type
|
||||
for G in (UG, DG):
|
||||
edges = nx.draw_networkx_edges(G, pos, arrows=True)
|
||||
assert len(edges) == len(G.edges)
|
||||
assert isinstance(edges[0], mpl.patches.FancyArrowPatch)
|
||||
|
||||
# Use LineCollection when arrows=False, regardless of graph type
|
||||
for G in (UG, DG):
|
||||
edges = nx.draw_networkx_edges(G, pos, arrows=False)
|
||||
assert isinstance(edges, mpl.collections.LineCollection)
|
||||
|
||||
# Default behavior when arrows=None: FAPs for directed, LC's for undirected
|
||||
edges = nx.draw_networkx_edges(UG, pos)
|
||||
assert isinstance(edges, mpl.collections.LineCollection)
|
||||
edges = nx.draw_networkx_edges(DG, pos)
|
||||
assert len(edges) == len(G.edges)
|
||||
assert isinstance(edges[0], mpl.patches.FancyArrowPatch)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("drawing_func", (nx.draw, nx.draw_networkx))
|
||||
def test_draw_networkx_arrows_default_undirected(drawing_func):
|
||||
import matplotlib.collections
|
||||
|
||||
G = nx.path_graph(3)
|
||||
fig, ax = plt.subplots()
|
||||
drawing_func(G, ax=ax)
|
||||
assert any(isinstance(c, mpl.collections.LineCollection) for c in ax.collections)
|
||||
assert not ax.patches
|
||||
plt.delaxes(ax)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("drawing_func", (nx.draw, nx.draw_networkx))
|
||||
def test_draw_networkx_arrows_default_directed(drawing_func):
|
||||
import matplotlib.collections
|
||||
|
||||
G = nx.path_graph(3, create_using=nx.DiGraph)
|
||||
fig, ax = plt.subplots()
|
||||
drawing_func(G, ax=ax)
|
||||
assert not any(
|
||||
isinstance(c, mpl.collections.LineCollection) for c in ax.collections
|
||||
)
|
||||
assert ax.patches
|
||||
plt.delaxes(ax)
|
||||
|
||||
|
||||
def test_edgelist_kwarg_not_ignored():
|
||||
# See gh-4994
|
||||
G = nx.path_graph(3)
|
||||
G.add_edge(0, 0)
|
||||
fig, ax = plt.subplots()
|
||||
nx.draw(G, edgelist=[(0, 1), (1, 2)], ax=ax) # Exclude self-loop from edgelist
|
||||
assert not ax.patches
|
||||
plt.delaxes(ax)
|
||||
|
||||
|
||||
def test_draw_networkx_edge_label_multiedge_exception():
|
||||
"""
|
||||
draw_networkx_edge_labels should raise an informative error message when
|
||||
the edge label includes keys
|
||||
"""
|
||||
exception_msg = "draw_networkx_edge_labels does not support multiedges"
|
||||
G = nx.MultiGraph()
|
||||
G.add_edge(0, 1, weight=10)
|
||||
G.add_edge(0, 1, weight=20)
|
||||
edge_labels = nx.get_edge_attributes(G, "weight") # Includes edge keys
|
||||
pos = {n: (n, n) for n in G}
|
||||
with pytest.raises(nx.NetworkXError, match=exception_msg):
|
||||
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)
|
||||
|
||||
|
||||
def test_draw_networkx_edge_label_empty_dict():
|
||||
"""Regression test for draw_networkx_edge_labels with empty dict. See
|
||||
gh-5372."""
|
||||
G = nx.path_graph(3)
|
||||
pos = {n: (n, n) for n in G.nodes}
|
||||
assert nx.draw_networkx_edge_labels(G, pos, edge_labels={}) == {}
|
||||
|
||||
|
||||
def test_draw_networkx_edges_undirected_selfloop_colors():
|
||||
"""When an edgelist is supplied along with a sequence of colors, check that
|
||||
the self-loops have the correct colors."""
|
||||
fig, ax = plt.subplots()
|
||||
# Edge list and corresponding colors
|
||||
edgelist = [(1, 3), (1, 2), (2, 3), (1, 1), (3, 3), (2, 2)]
|
||||
edge_colors = ["pink", "cyan", "black", "red", "blue", "green"]
|
||||
|
||||
G = nx.Graph(edgelist)
|
||||
pos = {n: (n, n) for n in G.nodes}
|
||||
nx.draw_networkx_edges(G, pos, ax=ax, edgelist=edgelist, edge_color=edge_colors)
|
||||
|
||||
# Verify that there are three fancy arrow patches (1 per self loop)
|
||||
assert len(ax.patches) == 3
|
||||
|
||||
# These are points that should be contained in the self loops. For example,
|
||||
# sl_points[0] will be (1, 1.1), which is inside the "path" of the first
|
||||
# self-loop but outside the others
|
||||
sl_points = np.array(edgelist[-3:]) + np.array([0, 0.1])
|
||||
|
||||
# Check that the mapping between self-loop locations and their colors is
|
||||
# correct
|
||||
for fap, clr, slp in zip(ax.patches, edge_colors[-3:], sl_points):
|
||||
assert fap.get_path().contains_point(slp)
|
||||
assert mpl.colors.same_color(fap.get_edgecolor(), clr)
|
||||
plt.delaxes(ax)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fap_only_kwarg", # Non-default values for kwargs that only apply to FAPs
|
||||
(
|
||||
{"arrowstyle": "-"},
|
||||
{"arrowsize": 20},
|
||||
{"connectionstyle": "arc3,rad=0.2"},
|
||||
{"min_source_margin": 10},
|
||||
{"min_target_margin": 10},
|
||||
),
|
||||
)
|
||||
def test_user_warnings_for_unused_edge_drawing_kwargs(fap_only_kwarg):
|
||||
"""Users should get a warning when they specify a non-default value for
|
||||
one of the kwargs that applies only to edges drawn with FancyArrowPatches,
|
||||
but FancyArrowPatches aren't being used under the hood."""
|
||||
G = nx.path_graph(3)
|
||||
pos = {n: (n, n) for n in G}
|
||||
fig, ax = plt.subplots()
|
||||
# By default, an undirected graph will use LineCollection to represent
|
||||
# the edges
|
||||
kwarg_name = list(fap_only_kwarg.keys())[0]
|
||||
with pytest.warns(
|
||||
UserWarning, match=f"\n\nThe {kwarg_name} keyword argument is not applicable"
|
||||
):
|
||||
nx.draw_networkx_edges(G, pos, ax=ax, **fap_only_kwarg)
|
||||
# FancyArrowPatches are always used when `arrows=True` is specified.
|
||||
# Check that warnings are *not* raised in this case
|
||||
with warnings.catch_warnings():
|
||||
# Escalate warnings -> errors so tests fail if warnings are raised
|
||||
warnings.simplefilter("error")
|
||||
nx.draw_networkx_edges(G, pos, ax=ax, arrows=True, **fap_only_kwarg)
|
||||
|
||||
plt.delaxes(ax)
|
||||
Reference in New Issue
Block a user