Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions docs/dev/plotting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Recommended interface and behavior of a `plot` method

Here we recommend the input arguments, return value, and behavior of the
`plot` method of a class.

## Requirements

1. **Convenience to interactive users.** This is the highest priority.
Compared to being called in a batch script as a library (for composing
more complicated plots or other purposes), the `plot` method is mainly
used in interactive sessions like ipython, jupyther, colab, PyCharm,
and python interpreter.
1. **Plot is customizable.** The plot should be customizable by the user after
`plot` returns. This is important because user may need to change the look
for presentation, paper, or just the style they prefer. One plot style does
not fit all.
1. **No unnecessary messages in interactive sessions.** It should not produce
any warning/error messages in normal usages in an interactive sessions.
See [#1890](https://github.com/quantumlib/Cirq/issues/1890#issue-473510953)
for an example of such message.
1. **No popups during tests.** It should not produce any pop-up windows during
tests.

## Recommendation

The `plot` method must produce a plot when there is no arguments in an
interactive session. The recommended way to achieve that is illustrated in the
example below.

```python
import matplotlib.pyplot as plt

class Foo:
...
def plot(self, ax: plt.Axes=None, **plot_kwargs: Any) -> plt.Axes:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an import matplotlib.pyplot as plt line. The example testing test is failing because plt is not imported at this point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, though the tests are green before the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. That was a bug in the tests. Not it did fail before the move to a subdir of docs. Fixed in #2307

show_plot = not ax
if show_plot:
fig, ax = plt.subplots(1, 1) # or your favorite figure setup
# Call methods of the ax instance like ax.plot to plot on it.
...
if show_plot:
fig.show()
return ax
```

This `plot` method works in 2 modes: *memory mode* and *interactive mode*,
signalled by the presence of the `ax` argument. When present, the method is
instructed to plot on the provided `ax` instance in memory. No plot is shown
on the screen. When absent, the code is in *interactive* mode, and it creates
a figure and shows it.

The returned `ax` instance can be used to further customize the plot if the
user wants to. Note that if we were to call `plt.show` instead of `fig.show`,
the customizations on the returned `ax` does not show up on subsequent call to
`plt.show`.

To satisfy requirement number 4, unit test codes should create an `ax` object
and pass it into the `plot` method like the following example.

```python
def test_foo_plot():
# make a Foo instance foo
figure, ax = plt.subplots(1, 1)
foo.plot(ax)
# assert on the content of ax here if necessary.
```

This does not produce a pop-up window because `fig.show` is not called.


## Classes that produce multi-axes plot

Some classes contain complicated data and plotting on a single `ax` is
not sufficient. The `plot` method of such a class should take an optional
`axes` argument that is a list of `plt.Axes` instances.

```python
class Foo:
...
def plot(self, axes: List[plt.Axes]=None, **plot_kwargs: Any) -> List[plt.Axes]:
show_plot = not axes
if show_plot:
_, axes = plt.subplots(1, 2) # or your favorite figure setup
elif len(axes) != 2: # your required number of axes
raise ValueError('your error message')
# Call methods of the axes[i] objects to plot on it.
...
if show_plot:
fig.show()
return axes
```

The reason we don't recommend passing a `plt.Figure` argument is that, the
`plot` method has no information on which `plt.Axes` objects to plot on if
there are more `plt.Axes` in the figure than what the method needs. The caller
is responsible for passing in correct number of `Axes` instances.

The `plot` method can be tested similarly.

## PyCharm issue

As of this writing in October 2019, running a script calling a `plot` method
in PyCharm does not pop up a window with the figure. A call to `plt.show()`
is needed to show it. We believe this is a PyCharm-specific issue because
the same code works in Python interpreter.

## References
* Issue #1890 "Plotting code should not call `show`"
* PR #2097
* PR #2286
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ User Documentation
qudits
development
examples
dev/plotting


API Reference
Expand Down