There are several ways to approach controlling a matplotlib plot using ipywidgets. Below I’ve created the output I think you’re looking for using each of the options. The methods are listed in what feels like the natural order of discovery, however, I would recommend trying them in this order: 4, 2, 1, 3
Approach 1 – inline backend
If you use %matplotlib inline
then matplotlib figures will not be interactive and you will need to recreate the entire plot every time
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
@interact
def graph(
Spiral=True,
n=2000,
x1=50,
y1=50,
z1=50,
k1=300,
vlc=(0.1, 1, 0.01),
vuc=(0.1, 1, 0.01),
):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
if Spiral == False:
x = 0
y = 0
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
ax1.scatter(x, y, s=3, color="k")
vu = np.quantile(img, vuc)
vl = np.quantile(img, vlc)
ax2.imshow(img, vmin=vl, vmax=vu)
Approach 2 – interactive backend + cla
You can use one of the interactive maptlotlib backends to avoid having to completely regenerate the figure every time you change. To do this the first approach is to simply clear the axes everytime the sliders change using the cla
method.
This will work with either %matplotlib notebook
or %matplotlib ipympl
. The former will only work in jupyter notebook and the latter will work in both jupyter notebook and juptyerlab. (Installation info for ipympl here: https://github.com/matplotlib/ipympl#installation)
%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, interactive, interactive_output
# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
@interact
def graph(
Spiral=True,
n=2000,
x1=50,
y1=50,
z1=50,
k1=300,
vlc=(0.1, 1, 0.01),
vuc=(0.1, 1, 0.01),
):
ax1.cla()
ax2.cla()
if Spiral == False:
x = 0
y = 0
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
ax1.scatter(x, y, s=3, color="k")
vu = np.quantile(img, vuc)
vl = np.quantile(img, vlc)
ax2.imshow(img, vmin=vl, vmax=vu)
Approach 3 – interactive backend + set_data
Totally clearing the axes can be inefficient when you are plotting larger datasets or have some parts of the plot that you want to persist from one interaction to the next. So you can instead use the set_data
and set_offsets
methods to update what you have already drawn.
%matplotlib ipympl
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact, interactive, interactive_output
# load fake image data
from matplotlib import cbook
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12,5))
scat = ax1.scatter([0]*2000,[0]*2000,s=3, color="k")
im = ax2.imshow(img)
out = widgets.Output()
display(out)
@interact
def graph(
Spiral=True,
n=2000,
x1=50,
y1=50,
z1=50,
k1=300,
vlc=(0.1, 1, 0.01),
vuc=(0.1, 1, 0.01),
):
if Spiral == False:
x = 0
y = 0
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
scat.set_offsets(np.c_[x, y])
# correctly scale the x and y limits
ax1.dataLim = scat.get_datalim(ax1.transData)
ax1.autoscale_view()
vu = np.quantile(img, vuc)
vl = np.quantile(img, vlc)
im.norm.vmin = vl
im.norm.vmax = vu
Approach 4 – mpl_interactions
Using set_offsets
and equivalent set_data
will be the most performant solution, but can also be tricky to figure out how to get it work and even trickier to remember. To make it easier I’ve creted a library (mpl-interactions) that automates the boilerplate of approach 3.
In addition to being easy and performant this has the advantage that you aren’t responsible for updating the plots, only for returning the correct values. Which then has the ancillary benefit that now functions like spiral
can be used in other parts of your code as they just return values rather than handle plotting.
The other advantage is that mpl-interactions can also create matplotlib widgets so this is the only approach that will also work outside of a notebook.
%matplotlib ipympl
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
import mpl_interactions.ipyplot as iplt
img = plt.imread(cbook.get_sample_data("grace_hopper.jpg")).mean(axis=-1)
# define the functions to be plotted
def spiral(Spiral=False, n=2000, x1=50, y1=50, z1=50, k1=300):
if Spiral == False:
x = 0
y = 0
return x, y
else:
angle = np.linspace(x1, y1 * 1 * np.pi, n)
radius = np.linspace(z1, k1, n)
x = radius * np.cos(angle) + 150
y = radius * np.sin(angle) + 150
return x, y
def vmin(vuc, vlc):
return np.quantile(img, vlc)
def vmax(vlc, vuc):
return np.quantile(img, vuc)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
controls = iplt.scatter(
spiral,
Spiral={(True, False)},
n=np.arange(1800, 2200),
x1=(25, 75),
y1=(25, 75),
z1=(25, 75),
k1=(200, 400),
parametric=True,
s=3,
c="black",
ax=ax1,
)
controls = iplt.imshow(
img,
vmin=vmin,
vmax=vmax,
vuc=(0.1, 1, 1000),
vlc=(0.1, 1, 1000),
controls=controls[None],
ax=ax2,
)