Skip to content

Commit

Permalink
Add visualization to retworkx for networkx users guide (Qiskit#344)
Browse files Browse the repository at this point in the history
* Add visualization to retworkx for networkx users guide

This commit adds a section to the retworkx for networkx users guide on
visualizations. This is important especially for the ``mpl_draw``
function since it's mostly identical to the ``networkx_drawer`` function
but there are some key differences to keep in mind.

This commit also makes small change to the graphviz drawer, the name of
the function is renamed from `pydot_draw` to `graphviz_draw` and the
Python module is renamed from `pydot` to `graphviz`. This was done to
be more descriptive as graphviz is the backend not pydot. It also makes
it easier to migrate away from pydot in the future if we needed to since
the key piece is using graphviz not a particular python interface to it.

Fixes Qiskit#341

* Update release note too

* Rename optional target to graphviz
  • Loading branch information
mtreinish authored May 29, 2021
1 parent 403dcbc commit aad90f7
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ retworkx/*pyd
*.stestr/
*.ps
*.png
*.svg
*.jpg
2 changes: 2 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ type functions in the algorithms API but can be run with a
retworkx.random_layout
retworkx.spring_layout

.. _layout-functions:

Layout Functions
================

Expand Down
64 changes: 64 additions & 0 deletions docs/source/networkx.rst
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,70 @@ This difference with retworkx is primarily because numpy exposes a public C
interface which retworkx can interface with directly, while the other
libraries and types only expose Python APIs.

Visualization Functions
-----------------------

NetworkX provides a native drawer with a matplotlib drawer (the
``networkx_drawer*`` functions) and then functions to interface with
``pygraphviz`` and ``pydot`` to enable visualization with graphviz via those
libraries (in addition to functions to serialize graphs in formats other
graph visualization tools can use). NetworkX also provides several functions
`layout functions <https://networkx.org/documentation/stable/reference/drawing.html#module-networkx.drawing.layout>`__
for generating different layouts that can be used for visualizing the graph.


retworkx has drawer functions with 2 visualization backends, matplotlib
(:func:`~retworkx.visualization.mpl_draw`) and graphviz
(:func:`~retworkx.visualization.graphviz_draw`). Unlike networkx the
:func:`~retworkx.visualization.graphviz_draw` will handle calling graphviz and
generate an image file. For layout functions retworkx has a similar variety of
:ref:`layout-functions`, however it should be noted that retworkx's functions
are strictly 2 dimensional. The also return a :class:`~retworkx.Pos2DMapping`
custom return type which acts as read-only dictionary (which is different from
networkx which returns a normal dictionary that can be modified).

Matplotlib Drawers
^^^^^^^^^^^^^^^^^^

The retwork function :func:`~retworkx.visualization.mpl_draw` function is
basically equivalent to the networkx function ``draw_networkx`` (it was
actually originally forked from the networkx drawer). However, there are some
key differences to keep in mind between the networkx and retworkx matplotlib
drawer.

``networkx.draw_networkx`` and ``retworkx.mpl_draw`` differences:

.. list-table::
:header-rows: 1

* - networkx
- retworkx
- Notes
* - ``nodelist``
- ``node_list``
-
* - ``edgelist``
- ``edge_list``
-
* - ``arrowsize``
- ``arrow_size``
-
* - ``labels``
- ``labels``
- For ``networkx_drawer`` ``labels`` is a dict of nodes to their label,
while retworkx's ``mpl_drawer`` ``labels`` is a callback function
that will be passed a node's data payload and expected to return the
node's label
* - ``networkx.draw_networkx_edge_labels()``
- ``edge_labels``
- NetworkX's ``networkx_drawer`` doesn't have an option for edge labels
and instead adding labels is only exposed via a separate function
``draw_networkx_edge_labels()`` which requires the ``pos`` dictionary
from the original visualization to be used. retworkx's ``edge_labels``
kwarg takes a callback function that will be passed an edge's data
payload and expected to return the label.


.. _networkx_converter:

Converting from a networkx graph
Expand Down
2 changes: 1 addition & 1 deletion docs/source/visualization.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Visualization API
:toctree: stubs

retworkx.visualization.mpl_draw
retworkx.visualization.pydot_draw
retworkx.visualization.graphviz_draw
6 changes: 3 additions & 3 deletions releasenotes/notes/pydot-drawer-c5df0aa830679748.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
features:
- |
Added a new `Graphviz <https://graphviz.org/>`__ based drawer function,
:func:`~retworkx.visualization.pydot_draw`, to the
:func:`~retworkx.visualization.graphviz_draw`, to the
:mod:`retworkx.visualization` module. This function requires that
Graphviz is installed locally and adds two new optional dependencies,
`pydot <https://pypi.org/project/pydot/>`__ which is used to call Graphviz
Expand All @@ -15,7 +15,7 @@ features:
.. jupyter-execute::
import retworkx
from retworkx.visualization import pydot_draw
from retworkx.visualization import graphviz_draw
def node_attr(node):
if node == 0:
Expand All @@ -26,4 +26,4 @@ features:
return {'color': 'red', 'fillcolor': 'red', 'style': 'filled'}
graph = retworkx.generators.directed_star_graph(weights=list(range(32)))
pydot_draw(graph, node_attr_fn=node_attr, method='sfdp')
graphviz_draw(graph, node_attr_fn=node_attr, method='sfdp')
4 changes: 2 additions & 2 deletions retworkx/visualization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

__all__ = [
"mpl_draw",
"pydot_draw",
"graphviz_draw",
]

from .matplotlib import mpl_draw
from .pydot import pydot_draw
from .graphviz import graphviz_draw
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
except ImportError:
HAS_PYDOT = False

__all__ = ["pydot_draw"]
__all__ = ["graphviz_draw"]


def pydot_draw(
def graphviz_draw(
graph,
node_attr_fn=None,
edge_attr_fn=None,
Expand All @@ -30,7 +30,7 @@ def pydot_draw(
method=None,
):
"""Draw a :class:`~retworkx.PyGraph` or :class:`~retworkx.PyDiGraph` object
using graphviz via pydot
using graphviz
.. note::
Expand Down Expand Up @@ -87,7 +87,7 @@ def pydot_draw(
"""
if not HAS_PYDOT:
raise ImportError(
"Pydot and Pillow are necessary to use pydot_draw() "
"Pydot and Pillow are necessary to use graphviz_draw() "
"it can be installed with 'pip install pydot pillow'"
)
try:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ def readme():
install_requires=['numpy>=1.16.0'],
extras_require={
'mpl': ['matplotlib>=3.0'],
'pydot': ['pydot>=1.4', 'pillow>=5.4'],
'graphviz': ['pydot>=1.4', 'pillow>=5.4'],
}
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import unittest

import retworkx
from retworkx.visualization import pydot_draw
from retworkx.visualization import graphviz_draw

try:
import pydot
Expand All @@ -37,12 +37,12 @@ def _save_image(image, path):
@unittest.skipUnless(
HAS_PYDOT, "pydot and graphviz are required for running these tests"
)
class TestPyDotDraw(unittest.TestCase):
class TestGraphvizDraw(unittest.TestCase):
def test_draw_no_args(self):
graph = retworkx.generators.star_graph(24)
image = pydot_draw(graph)
image = graphviz_draw(graph)
self.assertIsInstance(image, PIL.Image.Image)
_save_image(image, "test_pydot_draw.png")
_save_image(image, "test_graphviz_draw.png")

def test_draw_node_attr_fn(self):
graph = retworkx.PyGraph()
Expand All @@ -63,9 +63,9 @@ def test_draw_node_attr_fn(self):
}
)
graph.add_edge(0, 1, dict(label="1", name="1"))
image = pydot_draw(graph, lambda node: node)
image = graphviz_draw(graph, lambda node: node)
self.assertIsInstance(image, PIL.Image.Image)
_save_image(image, "test_pydot_draw_node_attr.png")
_save_image(image, "test_graphviz_draw_node_attr.png")

def test_draw_edge_attr_fn(self):
graph = retworkx.PyGraph()
Expand All @@ -86,9 +86,9 @@ def test_draw_edge_attr_fn(self):
}
)
graph.add_edge(0, 1, dict(label="1", name="1"))
image = pydot_draw(graph, lambda node: node, lambda edge: edge)
image = graphviz_draw(graph, lambda node: node, lambda edge: edge)
self.assertIsInstance(image, PIL.Image.Image)
_save_image(image, "test_pydot_draw_edge_attr.png")
_save_image(image, "test_graphviz_draw_edge_attr.png")

def test_draw_graph_attr(self):
graph = retworkx.PyGraph()
Expand All @@ -110,32 +110,32 @@ def test_draw_graph_attr(self):
)
graph.add_edge(0, 1, dict(label="1", name="1"))
graph_attr = {"bgcolor": "red"}
image = pydot_draw(
image = graphviz_draw(
graph, lambda node: node, lambda edge: edge, graph_attr
)
self.assertIsInstance(image, PIL.Image.Image)
_save_image(image, "test_pydot_draw_graph_attr.png")
_save_image(image, "test_graphviz_draw_graph_attr.png")

def test_image_type(self):
graph = retworkx.directed_gnp_random_graph(50, 0.8)
image = pydot_draw(graph, image_type="jpg")
image = graphviz_draw(graph, image_type="jpg")
self.assertIsInstance(image, PIL.Image.Image)
_save_image(image, "test_pydot_draw_image_type.jpg")
_save_image(image, "test_graphviz_draw_image_type.jpg")

def test_method(self):
graph = retworkx.directed_gnp_random_graph(50, 0.8)
image = pydot_draw(graph, method="sfdp")
image = graphviz_draw(graph, method="sfdp")
self.assertIsInstance(image, PIL.Image.Image)
_save_image(image, "test_pydot_method.png")
_save_image(image, "test_graphviz_method.png")

def test_filename(self):
graph = retworkx.generators.grid_graph(20, 20)
pydot_draw(
graphviz_draw(
graph,
filename="test_pydot_filename.svg",
filename="test_graphviz_filename.svg",
image_type="svg",
method="neato",
)
self.assertTrue(os.path.isfile("test_pydot_filename.svg"))
self.assertTrue(os.path.isfile("test_graphviz_filename.svg"))
if not SAVE_IMAGES:
self.addCleanup(os.remove, "test_pydot_filename.svg")
self.addCleanup(os.remove, "test_graphviz_filename.svg")

0 comments on commit aad90f7

Please sign in to comment.