How to efficiently replace element in plot with ipywidgets? - python

How can I efficiently display similar plots with ipywidgets using Jupyter Notebook?
I wish to plot interactively a heavy plot (heavy in the sense that it has lots of data points and takes some time to plot it) and modify a single element of it using interact from ipywidgets without replotting all the complicated plot. Is there a builtin functionality to do this?
basically what I'm trying to do is
import numpy as np
import matplotlib.pyplot as plt
from ipywidgets import interact
import matplotlib.patches as patches
%matplotlib inline #ideally nbagg
def complicated plot(t):
plt.plot(HEAVY_DATA_SET)
ax = plt.gca()
p = patches.Rectangle(something_that_depends_on_t)
ax.add_patch(p)
interact(complicatedplot, t=(1, 100));
Right now it takes up to 2 seconds for each replot. I expect there are ways to keep the figure there and just replace that rectangle.
A hack would be to create a figure of the constant part, make it background to the plot and just plot the rectangle part. but the sounds too dirty
Thank you

This is an rough example of an interactive way to change a rectangle width (I'm assuming you are in an IPython or Jupyter notebook):
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import ipywidgets
from IPython.display import display
%matplotlib nbagg
f = plt.figure()
ax = plt.gca()
ax.add_patch(
patches.Rectangle(
(0.1, 0.1), # (x,y)
0.5, # width
0.5, # height
)
)
# There must be an easier way to reference the rectangle
rect = ax.get_children()[0]
# Create a slider widget
my_widget = ipywidgets.FloatSlider(value=0.5, min=0.1, max=1, step=0.1, description=('Slider'))
# This function will be called when the slider changes
# It takes the current value of the slider
def change_rectangle_width():
rect.set_width(my_widget.value)
plt.draw()
# Now define what is called when the slider changes
my_widget.on_trait_change(change_rectangle_width)
# Show the slider
display(my_widget)
Then if you move the slider, the width of the rectangle will change. I'll try to tidy up the code, but you may have the idea. To change the coordinates, you have to do rect.xy = (x0, y0), where x0 and y0 are new coordinates.

Related

MatPlotLib with ion() does not show window

If I run the following code:
import matplotlib.pyplot as plt
import numpy as np
#plt.ion()
while True:
print('loop')
x = range(10)
y = np.random.rand(10)
plt.scatter(x, y)
plt.show()
Then I see a scatter plot displayed on my screen. Then each time I close the window for the plot, it displays a new plot with new data.
However, if I uncomment the line plt.ion(), nothing is displayed at all. There is no window created, and the program just continues through the loop, printing out 'loop'.
I want to be able to display a graph, and then return to the code automatically, with the graph still displayed. How can I do this?
If you want to plot on top of the same figure window, rather than generating a new window at every iteration the following will work:
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots(1, 1)
while True:
# If wanting to see an "animation" of points added, add a pause to allow the plotting to take place
plt.pause(1)
x = range(10)
y = np.random.rand(10)
ax.scatter(x, y)
The result you see will depend on the which matplotlib backend you are using. If you're wanting to see the new points being added you should use Qt4 or Qt5

Matplotlib widget used on a plot produced by another matplotlib widget

I am trying to use matplotlib LassoSelector to select some points from a scatter plot and produce a separate figure for selected points only. When I try to use another matplotlib widget on the second plot it doesn't work but there is no error or warning message. Below is a minimal example with LassoSelector and SpanSelector used.
I tried other widgets too; the Button widget displays the button but the action on the button press is not performed.
import numpy as np
from matplotlib.pyplot import *
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
def onselect(verts):
global xys,data
#get indexes of selected points
path = Path(verts)
xysn = xys.get_offsets()
ind = np.nonzero([path.contains_point(xy) for xy in xysn])[0]
#plot the second figure
fig=figure(2)
ax=fig.add_subplot(111)
ax.hist(data[:,0][ind],10)
#this should be executed when SpanSelector is used
def action(min,max):
print min,max
#try to do SpanSelector (this fails)
span=SpanSelector(ax,action,'horizontal')
show()
#initialize a figure
fig=figure(1)
ax=fig.add_subplot(111)
#create data
data=np.array([[1,6], [4,8],[0,4],[4,2],[9,6],[10,8],[2,2],[5,5],[0,4],[4,5]])
#plot data
xys=ax.scatter(data[:,0],data[:,1])
#select point by drawing a path around them
lasso = LassoSelector(ax, onselect=onselect)
show()
matplotlib widgets are event driven, so wait for user input. The problem with you code is you are trying to create a new figure with a new event handler SpanSelector. I'm not sure if you can add new events as a result of previous ones and with SpanSelector commented out, I get the following error,
QCoreApplication::exec: The event loop is already running
So the new event, LassoSelector is not registered and user input is not picked up (and the new figure doesn't appear). It is better to create all figures and register all possible events at the beginning of the code. The following should be closer to what you want to do,
import numpy as np
from matplotlib.pyplot import *
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
#this should be executed when LassoSelector is used
def onselect(verts):
global xys,data
#get indexes of selected points
path = Path(verts)
xysn = xys.get_offsets()
ind = np.nonzero([path.contains_point(xy) for xy in xysn])[0]
#Clear and update bar chart
h, b = np.histogram(data[:,0][ind],10)
for rect, bars in zip(rects, h):
rect.set_height(bars)
ax2.bar(mb, h, align='center')
draw()
#this should be executed when SpanSelector is used
def action(min,max):
print min,max
#initialize figures
fig1=figure(1)
ax1=fig1.add_subplot(111)
fig2=figure(2)
ax2=fig2.add_subplot(111)
#create data
data=np.array([[1,6],[4,8],[0,4],[4,2],[9,6],[10,8],[2,2],[5,5],[0,4],[4,5]])
#plot data
xys=ax1.scatter(data[:,0],data[:,1])
#Plot initial histogram of all data
h, b = np.histogram(data[:,0],10)
mb = [0.5*(b[i]+b[i+1]) for i in range(b.shape[0]-1)]
rects = ax2.bar(mb, h, align='center')
#Register lasso selector
lasso = LassoSelector(ax1, onselect=onselect)
#Register SpanSelector
span=SpanSelector(ax2,action,'horizontal')
show()
Note, in order to update bar charts, it's a little more tricky than plots so I used this answer here Dynamically updating a bar plot in matplotlib
For some reason, the histogram figure 2 only updates when you click on it. I would consider using a single figure with two axes for this which may be easier to work with,
fig, ax = subplots(2,1)
ax1 = ax[0]; ax2 = ax[1]

Is it possible to control matplotlib in predictable way?

The following code behaves absolutely ununderstandable for me:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
img=mpimg.imread('stinkbug.png')
imgplot = plt.imshow(img)
circle = plt.Circle((0, 0), radius=100, fc='y')
plt.figure(0)
#plt.show(imgplot)
plt.show(circle)
It displays two figures, although no only one show() function called.
It displays stinkbug in figure, although imgplot was never shown.
It does not display circle, although circle was shown.
You are telling matplotlib to do the following:
Load an image (... so far so good)
Create a figure displaying the image (Figure 1 by default) (... so far so good)
Create a patch object that represents a circle. This is not associated with any axes or anything where it could be drawn.
Create an empty Figure 0. Why? We may never know.
Call plt.show() with a patch as an argument. Because matplotlib is being nice, it ignores this argument and just displays the two figures as predicted.
Some Notes
Patch objects are just representations of a shape. You have to plot them somewhere for them to work.
plt.show() just displays all the figures if you are not in interactive mode.
A Solution
Given all that, here is what I think you were trying to do:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
img = mpimg.imread('stinkbug.png')
circle = plt.Circle((0, 0), radius=100, fc='y')
fig, ax = plt.subplots()
ax.imshow(img)
ax.add_artist(circle)
fig.show()
subplots creates both a figure and axes for you. You can then use ax.imshow to display the image and ax.add_artist to display the circle. fig.show() and plt.show() are identical in this case.

Expand 3D-Plot to canvas size

I would like to know how I can expand the 3D Plot to fit the canvas and if one can zoom in and out of the whole cube.
My goal is to make the Plot catch the mouse inside the whole frame, so you can turn the view. Only makeing the background white isn't enough for me. I want the Plot to file the canvas. Right now the axis tick labels can escape the plot frame. By expanding the plot this would no longer be the case, so I would also like to zoom out of the cube a bit. The standard zoom only changes the axis scale.
Look at the sample below to find out what I mean.
#! coding=utf-8
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def func(X,Y):
return 1/np.pi/2*np.exp(-(X**2+Y**2)/2)
x = np.linspace(-5,5,200)
X,Y = np.meshgrid(x,x)
plt.figure()
sub = plt.subplot(111, projection='3d')
sub.plot_surface(X,Y,func(X,Y))
plt.show()

drawing lines and circles on screen using matplotlib

I want to draw some lines and circles on the screen using of matplotlib. I do not need the X axis and Y axis. Is this possible? How can I do it?
You can hide the axes with axes.get_xaxis().set_visible(False) or by using axis('off').
Example:
from pylab import *
gca().get_xaxis().set_visible(False) # Removes x-axis from current figure
gca().get_yaxis().set_visible(False) # Removes y-axis from current figure
a = arange(10)
b = sin(a)
plot(a, b)
show() # Plot has no x and y axes
If you don't want axes, and are happy to work in the range 0-1:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
fig = plt.figure()
fig.patches.append(mpatches.Circle([0.5, 0.5], 0.25, transform=fig.transFigure))
fig.show()
There are a couple of benefits to using #Dhara's solution. The primary being you can use a data coordinate system which automatically scales to your data, but if you just want to draw a couple of shapes, my solution works pretty well.
Some useful documentation if you go down the route I have explained:
http://matplotlib.sourceforge.net/api/artist_api.html#matplotlib.patches.Circle
http://matplotlib.sourceforge.net/api/artist_api.html#matplotlib.lines.Line2D
http://matplotlib.sourceforge.net/api/artist_api.html#matplotlib.patches.Rectangle

Categories

Resources