I would like to use the interact function to create sliders where the user would input vector coordinates and plot these coordinates. The problem is that changing the input values doesn't make a new graph, would it be possible to make it work?
I'm working with jupyter notebook. My code
import panel as pn
import numpy as np
import matplotlib.pyplot as plt
from panel.interact import interact, interactive, fixed, interact_manual
pn.extension()
def f(u1,u2,v1,v2):
plt.clf()
vetores = np.array([[0,0,u1,u2], [u1,u2,v1,v2]])
X, Y, U, V = zip(*vetores)
plt.figure()
ax = plt.gca()
ax.quiver(X, Y, U, V, angles='xy', scale_units='xy', scale=1, color = ['r','g','b'])
ax.set_xlim([min(-1,u1-1, v1-1), max(u1+v1+1, v1+1)])
ax.set_ylim([min(-1,u2-1, v2-1), max(u2+v2+1, v2+1)])
plt.show()
interact(f, u1=2, u2=0, v1=2, v2=3)
You can use ipywidgets interactive plot. Matplotlib has quiver:
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np
def f(u1,u2,v1,v2):
plt.figure()
vetores = np.array([[0,0,u1,u2], [u1,u2,v1,v2]])
X, Y, U, V = zip(*vetores)
plt.quiver(X, Y, U, V, angles='xy', scale_units='xy', scale=1, color = ['r','g','b'])
ax = plt.gca()
ax.set_xlim([min(-1,u1-1, v1-1), max(u1+v1+1, v1+1)])
ax.set_ylim([min(-1,u2-1, v2-1), max(u2+v2+1, v2+1)])
plt.show()
interactive_plot = interactive(f, u1=2, u2=0, v1=2, v2=3)
interactive_plot
Your code was adapted into my answer here.
It works in notebooks in Jupyter sessions launched from the link at the bottom there.
It also works in notebooks in Jupyter sessions launched via the holoviz panel MyBinder launch here.
Using Panel
Or using Panel in combination with Matplotlib based on upper part of here and returning a proper Matplotlib figure based on here:
import panel as pn
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
pn.extension()
title = '## Quiver Panel controller'
def f(u1,u2,v1,v2):
pl = plt.figure()
vetores = np.array([[0,0,u1,u2], [u1,u2,v1,v2]])
X, Y, U, V = zip(*vetores)
pl.add_subplot(111).quiver(X, Y, U, V, angles='xy', scale_units='xy', scale=1, color = ['r','g','b'])
ax = plt.gca()
ax.set_xlim([min(-1,u1-1, v1-1), max(u1+v1+1, v1+1)])
ax.set_ylim([min(-1,u2-1, v2-1), max(u2+v2+1, v2+1)])
plt.close(pl)
return pl
interact = pn.interact(f, u1=2, u2=0, v1=2, v2=3)
pn.Row(
pn.Column(title, interact[0], sizing_mode="fixed", width=300),
interact[1]
)
#interact # use this if don't want side-by-side
The layout is nicer in this example, but ipywidgets, which was used in the above option, can be used to arrange the controls side-by-side as well, see bottom of post here for an example.
I would imagine there's a way to make it simpler and instead use return plt.gcf() based on this and associated note in FAQ:
"A: Matplotlib pyplot users often use %matplotlib inline, which shows plots as a "side effect" in a Jupyter notebook, rather than using the cell's return value like Python literals and other objects do. Panel callbacks like those accepted for pn.interact() work on the return value of the callback, which is then provided as the return value of the cell, and thus directly display without any requirements for side effects. So, if you create a Matplotlib plot that would magically appear via %matplotlib inline, for Panel you need to ensure that the callback actually returns a value, rather than counting on this side effect. Specifically, if you have a callback with some Matplotlib plotting calls, you can add return plt.gcf() to your callback to make the current figure be returned, which will ensure that your plot is displayed properly."
However, I wasn't able to easily find the combination where it worked and I didn't see two plots. In fact, just trying the example code there results in two plots as well, only the upper one updating via the slider. The approach earlier in that thread produces no such artifact.
Related
I am an artist trying to wrap my head around generative/procedural design using NumPy and various assorted tools in jupyter notebook.
I have some code https://github.com/GreySoulX/Circle-generator/blob/main/BrokenCircles.ipynb (see below) that will generate a number of concentric circles of random radius and output them as SVG code. I can get it to display, and I can even get the SVG output with the basic code, but when put it all in a functions and call it with interactive() my saved files come out empty rather that what is shown in my notebook with widgets.VBox() .
Where can I fix this? Am I just missing this by a million miles?
import numpy as np
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from IPython.display import display, Markdown, clear_output
from ipywidgets import widgets
from ipywidgets import interact, interact_manual, interactive, Button
def make_circles(n_circles=100,min_radius=0.05, max_radius=9.0, debug=False, Refresh=False ):
x_bounds = [-10, 10]
y_bounds = [-10, 10]
circles = []
for i in range(n_circles):
c = np.array([0, 0])
r = np.unique(np.sort(np.round(np.random.uniform(min_radius,max_radius,1),2)))
circles.append((c, r))
circles
circle_patches = []
for c, r in circles:
circle_patches.append(mpatches.Circle(c, r, fill=None, edgecolor='black'))
fig, ax = plt.subplots(figsize=(20, 20))
if not debug:
plt.grid(False)
plt.axis('off')
ax.set_aspect('equal')
ax.set_xlim(x_bounds)
ax.set_ylim(y_bounds)
collection = PatchCollection(circle_patches, match_original=True)
ax.add_collection(collection)
return fig, ax
w = interactive(make_circles,
n_circles=(1,300,1),
min_radius=(0.00, 2.0, 0.1),
max_radius=(2.0, 20, 0.5))
#------Button----------
button = widgets.Button(description='Save as...')
out = widgets.Output()
def on_button_clicked(_):
#link function with output
with out:
#what happens when we hit button
#clear_output()
print('Saving Files...')
plt.savefig('SomeFile.svg', bbox_inches = 'tight', pad_inches = 0)
plt.savefig('SomeFile.png', bbox_inches = 'tight', pad_inches = 0)
# linking button and function together using a button's method
button.on_click(on_button_clicked)
# displaying Circles and button
widgets.VBox([button,out,w])
#------End Button------------
What's happening
This is an issue with how the inline backend handles closing figures and what plt.savefig does internally. The way static figures are displayed in notebooks (i.e. when not using ipympl) is that at the end of cell execution (or in this case at the end of the callback from the slider) the current figure is closed and displayed.
However plt.savefig expects there to be a currently open figure as it calls plt.gcf (get current figure) internally which either grabs the most recently active figure or creates a new empty figure if no figures are active.
So when you did this not in functions the figure wasn't closed until the cell was finished executing and so plt.savefig was able to find the figure. However when you moved to functions it was no longer able to find the current figure.
There are two basic solutions to this.
Solutions
1. Global fig
You can lift figure to the global scope and use fig.savefig - this makes sure that both the plot updating method and the saving method are refering to the same fig.
def make_circles(n_circles=100,min_radius=0.05, max_radius=9.0, debug=False, Refresh=False ):
global fig
...
fig, ax = plt.subplots()
....
def on_button_clicked(_):
global fig
...
fig.savefig('SomeFile.svg', bbox_inches = 'tight', pad_inches = 0)
2 - interactive backend
Use one of the interactive backends such as %matplotlib qt or %matplotlib ipympl. If you are working in a notebook or jupyterlab then i'd recommend ipympl which you can install with pip install ipympl.
With these backends the same closing of the figure does not automatically happen, so you can structure your code like this:
%matplotlib ipympl
fig, ax = plt.subplots()
def make_circles(n_circles=100,min_radius=0.05, max_radius=9.0, debug=False, Refresh=False ):
ax.cla() # clear all the artists off the axes
...
# use the same axes for `add_collection`
def on_button_clicked(_):
# you can now use `plt.savefig` but I would still recommend using `fig.savefig`
I am trying to create a ipywidget interface with a matplotlib figure that updates upon changing a slider. It works in principle, but it always creates an extra figure.
Here's the code:
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import widgets
from IPython.display import display, clear_output
# make a grid
x = np.linspace(0, 5, 100)
X, Y = np.meshgrid(x, x)
# create the layout with slider
out = widgets.Output(layout=widgets.Layout(height='300px', width = '400px', border='solid'))
slider = widgets.IntSlider(value=1, min=1, max=5)
w = widgets.VBox(children=(out, slider))
# axes to plot into
ax = plt.axes()
display(w)
def update(value):
i = slider.value
Z = np.exp(-(X / i)**2 - (Y / i)**2)
ax.pcolormesh(x, x, Z, vmin=0, vmax=1, shading='auto')
with out:
clear_output(wait=True)
display(ax.figure)
slider.observe(update)
update(None)
And here's the undesired output
The widget works, and only the upper output is updated, but I do not understand why the lower output also exists or how to get rid of it. Am I missing something obvious?
You can use the widget backend, %matplotlib widget, which I think is designed for this. You'll need to put %matplotlib widget at the top (or before matplotlib stuff is brought in).
Update: Also some guidance form matplotlib here and below, emphasis added.
Note
To get the interactive functionality described here, you must be using
an interactive backend. The default backend in notebooks, the inline
backend, is not. backend_inline renders the figure once and inserts a
static image into the notebook when the cell is executed. Because the
images are static, they can not be panned / zoomed, take user input,
or be updated from other cells.
Your example can be reduced to:
%matplotlib widget
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import widgets
from IPython.display import display, clear_output
x = np.linspace(0, 5, 100)
X, Y = np.meshgrid(x, x)
slider = widgets.IntSlider(value=1, min=1, max=5)
ax = plt.axes()
display(slider)
def update(value):
i = slider.value
Z = np.exp(-(X / i)**2 - (Y / i)**2)
ax.pcolormesh(x, x, Z, vmin=0, vmax=1, shading='auto')
slider.observe(update)
update(None)
The answer by jayveesa is a really good way to avoid this output. I'd like to add an answer to the why of your question.
The widget works, and only the upper output is updated, but I do not understand why the lower output also exists or how to get rid of it. Am I missing something obvious?
An instructive thing to do here is to comment out the lines in update that display the figure
def update(value):
i = slider.value
Z = np.exp(-(X / i)**2 - (Y / i)**2)
ax.pcolormesh(x, x, Z, vmin=0, vmax=1, shading='auto')
# with out:
# clear_output(wait=True)
# display(ax.figure)
update(None)
if you do this you will see only one plot output. This is because when you use the the %matplotlib inline backend for jupyter notebooks then any figure that was created in a cell will be closed and displayed when the cell finishes running. (see explanation from a core matplotlib dev here: https://github.com/matplotlib/matplotlib/issues/18248#issuecomment-675177108). So the figure surrounded by a black outline is from the display(ax.figure) and the lower figure is from the figure that was created earlier.
I'm trying to make an interactive plot in the jupyter notebook but i don't know exactly how to implement it. Having a dataframe i run a simple regression that is then plotted to see the distribution. I'd like to be able to hover one of the points and get data associated with this point. How can i do that? Right now i can only produce a static plot
import pandas as pd
from sklearn import linear_model
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
net = pd.read_csv("network_ver_64.csv")
net = net[net.AWDT12 > 0]
x = net.LOAD_DAILY.values
y = net.AWDT12.values
x_lenght = int(x.shape[0])
y_lenght = int(y.shape[0])
x = x.reshape(x_lenght, 1)
y = y.reshape(y_lenght,1)
regr = linear_model.LinearRegression()
regr.fit(x, y)
plt.scatter(x, y, color='black')
plt.plot(x, regr.predict(x), color='blue', linewidth=1)
plt.xticks(())
plt.yticks(())
plt.show()
First of all it's clear that the %matplotlib inline backend does not allow for interaction, as it is inline (in the sense that the plots are images).
However even in the notebook you can get interaction using the %matplotlib notebook backend. A basic hover took is already implemented: Moving the mouse in the canvas shows the current mouse position in data coordinates in the lower right corner.
Of course you can obtain more sophisticated functionality by writing some custom code. E.g. we can modify the picking example a little bit as follows:
import matplotlib.pyplot as plt
%matplotlib notebook
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click on points')
line, = ax.plot(np.random.rand(100), 'o', picker=5) # 5 points tolerance
text = ax.text(0,0,"")
def onpick(event):
thisline = event.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = event.ind
text.set_position((xdata[ind], ydata[ind]))
text.set_text(zip(xdata[ind], ydata[ind]))
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
This now shows the data coordinates of the point the mouse has clicked.
You're pretty free to adapt this to any case you like and make it more pretty using the standard matplotlib tools.
This question explains how to change the "camera position" of a 3D plot in matplotlib by specifying the elevation and azimuth angles. ax.view_init(elev=10,azim=20), for example.
Is there a similar way to specify the zoom of the figure numerically -- i.e. without using the mouse?
The only relevant question I could find is this one, but the accepted answer to that involves installing another library, which then also requires using the mouse to zoom.
EDIT:
Just to be clear, I'm not talking about changing the figure size (using fig.set_size_inches() or similar). The figure size is fine; the problem is that the plotted stuff only takes up a small part of the figure:
The closest solution to view_init is setting ax.dist directly. According to the docs for get_proj "dist is the distance of the eye viewing point from the object point". The initial value is currently hardcoded with dist = 10. Lower values (above 0!) will result in a zoomed in plot.
Note: This behavior is not really documented and may change. Changing the limits of the axes to plot only the relevant parts is probably a better solution in most cases. You could use ax.autoscale(tight=True) to do this conveniently.
Working IPython/Jupyter example:
%matplotlib inline
from IPython.display import display
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Grab some test data.
X, Y, Z = axes3d.get_test_data(0.05)
# Plot a basic wireframe.
ax.view_init(90, 0)
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
plt.close()
from ipywidgets import interact
#interact(dist=(1, 20, 1))
def update(dist=10):
ax.dist = dist
display(fig)
Output
dist = 10
dist = 5
In the answers to how to dynamically update a plot in a loop in ipython notebook (within one cell), an example is given of how to dynamically update a plot inside a Jupyter notebook within a Python loop. However, this works by destroying and re-creating the plot on every iteration, and a comment in one of the threads notes that this situation can be improved by using the new-ish %matplotlib nbagg magic, which provides an interactive figure embedded in the notebook, rather than a static image.
However, this wonderful new nbagg feature seems to be completely undocumented as far as I can tell, and I'm unable to find an example of how to use it to dynamically update a plot. Thus my question is, how does one efficiently update an existing plot in a Jupyter/Python notebook, using the nbagg backend? Since dynamically updating plots in matplotlib is a tricky issue in general, a simple working example would be an enormous help. A pointer to any documentation on the topic would also be extremely helpful.
To be clear what I'm asking for: what I want to do is to run some simulation code for a few iterations, then draw a plot of its current state, then run it for a few more iterations, then update the plot to reflect the current state, and so on. So the idea is to draw a plot and then, without any interaction from the user, update the data in the plot without destroying and re-creating the whole thing.
Here is some slightly modified code from the answer to the linked question above, which achieves this by re-drawing the whole figure every time. I want to achieve the same result, but more efficiently using nbagg.
%matplotlib inline
import time
import pylab as pl
from IPython import display
for i in range(10):
pl.clf()
pl.plot(pl.randn(100))
display.display(pl.gcf())
display.clear_output(wait=True)
time.sleep(1.0)
Here is an example that updates a plot in a loop. It updates the data in the figure and does not redraw the whole figure every time. It does block execution, though if you're interested in running a finite set of simulations and saving the results somewhere, it may not be a problem for you.
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import time
def pltsin(ax, colors=['b']):
x = np.linspace(0,1,100)
if ax.lines:
for line in ax.lines:
line.set_xdata(x)
y = np.random.random(size=(100,1))
line.set_ydata(y)
else:
for color in colors:
y = np.random.random(size=(100,1))
ax.plot(x, y, color)
fig.canvas.draw()
fig,ax = plt.subplots(1,1)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for f in range(5):
pltsin(ax, ['b', 'r'])
time.sleep(1)
I put this up on nbviewer here.
There is an IPython Widget version of nbagg that is currently a work in progress at the Matplotlib repository. When that is available, that will probably be the best way to use nbagg.
EDIT: updated to show multiple plots
I'm using jupyter-lab and this works for me (adapt it to your case):
from IPython.display import clear_output
from matplotlib import pyplot as plt
import numpy as np
import collections
%matplotlib inline
def live_plot(data_dict, figsize=(7,5), title=''):
clear_output(wait=True)
plt.figure(figsize=figsize)
for label,data in data_dict.items():
plt.plot(data, label=label)
plt.title(title)
plt.grid(True)
plt.xlabel('epoch')
plt.legend(loc='center left') # the plot evolves to the right
plt.show();
Then in a loop you populate a dictionary and you pass it to live_plot():
data = collections.defaultdict(list)
for i in range(100):
data['foo'].append(np.random.random())
data['bar'].append(np.random.random())
data['baz'].append(np.random.random())
live_plot(data)
make sure you have a few cells below the plot, otherwise the view snaps in place each time the plot is redrawn.
If you don't want to clear all outputs, you can use display_id=True to obtain a handle and use .update() on it:
import numpy as np
import matplotlib.pyplot as plt
import time
from IPython import display
def pltsin(ax, *,hdisplay, colors=['b']):
x = np.linspace(0,1,100)
if ax.lines:
for line in ax.lines:
line.set_xdata(x)
y = np.random.random(size=(100,1))
line.set_ydata(y)
else:
for color in colors:
y = np.random.random(size=(100,1))
ax.plot(x, y, color)
hdisplay.update(fig)
fig,ax = plt.subplots(1,1)
hdisplay = display.display("", display_id=True)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for f in range(5):
pltsin(ax, colors=['b', 'r'], hdisplay=hdisplay)
time.sleep(1)
plt.close(fig)
(adapted from #pneumatics)
I've adapted #Ziofil answer and modified it to accept x,y as list and output a scatter plot plus a linear trend on the same plot.
from IPython.display import clear_output
from matplotlib import pyplot as plt
%matplotlib inline
def live_plot(x, y, figsize=(7,5), title=''):
clear_output(wait=True)
plt.figure(figsize=figsize)
plt.xlim(0, training_steps)
plt.ylim(0, 100)
x= [float(i) for i in x]
y= [float(i) for i in y]
if len(x) > 1:
plt.scatter(x,y, label='axis y', color='k')
m, b = np.polyfit(x, y, 1)
plt.plot(x, [x * m for x in x] + b)
plt.title(title)
plt.grid(True)
plt.xlabel('axis x')
plt.ylabel('axis y')
plt.show();
you just need to call live_plot(x, y) inside a loop.
here's how it looks:
The canvas.draw method of the figure dynamically updates its graphs, for the current figure:
from matplotlib import pyplot as plt
plt.gcf().canvas.draw()