I'm trying to embed the "home" function (i.e., "reset original review") to a widget button combined with other operations. Below is an example code I have.
import matplotlib.pyplot as plt
import matplotlib
from matplotlib.widgets import Button
from random import randrange
matplotlib.use('QtAgg')
plt.rcParams['toolbar'] = 'toolmanager' # switch this row to see the difference.
fig, ax = plt.subplots(nrows=1, figsize=(4,6))
ax.imshow([[1, 2], [5, 6]])
def change_color(event):
ax.imshow([[randrange(10), randrange(10)], [randrange(10), randrange(10)]])
plt.get_current_fig_manager().toolbar.home()
axprev0 = plt.axes([0.35, 0.05, 0.3, 0.0375])
bprev0 = Button(axprev0, 'Change Color')
bprev0.on_clicked(change_color)
plt.show()
My problem is that when plt.rcParams['toolbar'] = 'toolmanager' is on, it shows error message toolbar.home() won't work. The reason I need plt.rcParams['toolbar'] = 'toolmanager' is because it allows me to scroll the mouse wheel for zooming when I'm on the zoom or drag tool.
It seems that when we specify plt.rcParams['toolbar'] = 'toolmanager', it is using ToolbarQt. The ToolbarQt supports mouse wheel scrolling for zooming. While the name of the home function of ToolbarQt is different.
If we do not specify plt.rcParams['toolbar'] = 'toolmanager', plt is using NavigationToolbar2QT. However, it does not support mouse wheel scrolling for zooming.
plt.get_current_fig_manager().toolmanager.tools['home'].trigger(fig, event) # use this function when toolmanager
Related
I'm trying to use Matplotlib RangeSlider and everything is ok except "valinit" value. I just couldn't find how to use it.
valinit : tuple of float or None, default: None
The initial positions of the slider. If None the initial positions will be at the 25th and 75th percentiles of the range.
https://matplotlib.org/stable/api/widgets_api.html#matplotlib.widgets.RangeSlider
I want to make initial position of the slider as 0%-100%, in my case (0, totaltimest) not the default 25%-75%, but I couldn't figure it out. Can somebody help me please? An example would be great.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import RangeSlider
import soundfile as sf
file='A.wav' # mono audio file
data,fs=sf.read(file)
totaltimest=len(data)/fs
x = np.linspace(0, totaltimest, len(data))
fig, ax= plt.subplots(figsize=(10, 5))
ax.plot(x,data, label='Reference', color='red',alpha=0.5,linewidth=0.6)
ax.set_facecolor('#EFEFEF')
(yl1,yh1)=ax.get_ylim()
scroll1 = plt.axes([0.22, 0.01, 0.60, 0.03], facecolor='#EFEFEF')
scroll1v = RangeSlider(scroll1, 'Window', 0, totaltimest, valinit=None) # PROBLEM
def update(val):
pos1=scroll1v.val[0]
pos2=scroll1v.val[1]
ax.axis([pos1, pos2,yl1,yh1])
fig.canvas.draw_idle()
scroll1v.on_changed(update)
plt.tight_layout
plt.show()
Figured someone else might find this useful so better late than never, but give this a shot:
scroll1v = RangeSlider(scroll1, 'Window', valinit=(0,totaltimest))
Here's an example of how I used it in conjunction with Numpy arrays for my own project. I was previously working with two separate sliders, but recognizing that the Slider widget works differently than a RangeSlider (float vs. tuple), we have to format the argument for valinit as a tuple as well. Check it out:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import RangeSlider
# Creating axis and slider:
ax_rgb = plt.axes([0.23, 0.18, 0.10, 0.70]) # X-cord, Y-cord, Width, Height
sl_rgb = RangeSlider(ax_rgb,
label = 'RGB Array',
valinit = (np.max(rgb),np.min(rgb)),
valmax = np.max(rgb),
valmin = np.min(rgb),
orientation = 'vertical')
Depending on how comfortable you are with Matplotlib widgets, might not be a bad idea to indent and label your Slider attributes. Cheers!
Unfortunately it is not possible to create live plots in a google colab notebook using %matplotlib notebook like it is in a offline jupyter notebook on my PC.
I found two similar questions answering how to achieve this for plotly plots (link_1, link_2).
However I cannot manage to adapt it to matplotlib or do not know if that is possible at all.
I am following code from this tutorial here: GitHub link.
In particular I would like to run this code, which creates a callback plotting the reward per step over the training steps:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
class PlottingCallback(BaseCallback):
"""
Callback for plotting the performance in realtime.
:param verbose: (int)
"""
def __init__(self, verbose=1):
super(PlottingCallback, self).__init__(verbose)
self._plot = None
def _on_step(self) -> bool:
# get the monitor's data
x, y = ts2xy(load_results(log_dir), 'timesteps')
if self._plot is None: # make the plot
plt.ion()
fig = plt.figure(figsize=(6,3))
ax = fig.add_subplot(111)
line, = ax.plot(x, y)
self._plot = (line, ax, fig)
plt.show()
else: # update and rescale the plot
self._plot[0].set_data(x, y)
self._plot[-2].relim()
self._plot[-2].set_xlim([self.locals["total_timesteps"] * -0.02,
self.locals["total_timesteps"] * 1.02])
self._plot[-2].autoscale_view(True,True,True)
self._plot[-1].canvas.draw()
# Create log dir
log_dir = "/tmp/gym/"
os.makedirs(log_dir, exist_ok=True)
# Create and wrap the environment
env = make_vec_env('MountainCarContinuous-v0', n_envs=1, monitor_dir=log_dir)
plotting_callback = PlottingCallback()
model = PPO2('MlpPolicy', env, verbose=0)
model.learn(20000, callback=plotting_callback)
A hack that you can use, is use the same code that you would use on a jupyter notbook, create a button, and use JavaScript to click the button, fooling the frontend to request an update, so that it keeps updating the values.
Here it is an example that uses ipywidgets.
from IPython.display import display
import ipywidgets
progress = ipywidgets.FloatProgress(value=0.0, min=0.0, max=1.0)
import asyncio
async def work(progress):
total = 100
for i in range(total):
await asyncio.sleep(0.2)
progress.value = float(i+1)/total
display(progress)
asyncio.get_event_loop().create_task(work(progress))
button = ipywidgets.Button(description="This button does nothing... except send a\
socket request to google servers to receive updated information since the \
frontend wants to change..")
display(button,ipywidgets.HTML(
value="""<script>
var b=setInterval(a=>{
//Hopefully this is the first button
document.querySelector('#output-body button').click()},
1000);
setTimeout(c=>clearInterval(b),1000*60*1);
//Stops clicking the button after 1 minute
</script>"""
))
Dealing specifically with matplotlib is a bit more complicated, I thought I could simply call matplotlib plot on the asyncio function, but it really lags down the updates because it seems to do unnecessary rendering in the background where no one sees the plot. So another workaround is to update the plot on the code of the button update.
This code is also inspired by Add points to matlibplot scatter plot live and Matplotlib graphic image to base64
The reason being that it is unnecessary to create a plot figure for every plot, you can just modify the figure you already had. This of course means more code.
from IPython.display import display
import ipywidgets
import matplotlib.pyplot as plt
import numpy as np
import io
import base64
def pltToImg(plt):
s = io.BytesIO()
plt.savefig(s, format='png', bbox_inches="tight")
s = base64.b64encode(s.getvalue()).decode("utf-8").replace("\n", "")
#plt.close()
return '<img align="left" src="data:image/png;base64,%s">' % s
progress = ipywidgets.FloatProgress(value=0.0, min=0.0, max=1.0)
import asyncio
async def work(progress):
total = 100
for i in range(total):
await asyncio.sleep(0.5)
progress.value = float(i+1)/total
display(progress)
asyncio.get_event_loop().create_task(work(progress))
button = ipywidgets.Button(description="Update =D")
a=ipywidgets.HTML(
value="image here"
)
output = ipywidgets.Output()
plt.ion()
fig, ax = plt.subplots()
plot = ax.scatter([], [])
point = np.random.normal(0, 1, 2)
array = plot.get_offsets()
array = np.append(array, [point], axis=0)
plot.set_offsets(array)
plt.close()
ii=0
def on_button_clicked(b):
global ii
ii+=1
point=np.r_[ii,np.random.normal(0, 1, 1)]
array = plot.get_offsets()
array = np.append(array, [point], axis=0)
plot.set_offsets(array)
ax.set_xlim(array[:, 0].min() - 0.5, array[:,0].max() + 0.5)
ax.set_ylim(array[:, 1].min() - 0.5, array[:, 1].max() + 0.5)
a.value=(pltToImg(fig))
a.value+=str(progress.value)
a.value+=" </br>"
a.value+=str(ii)
button.on_click(on_button_clicked)
display(output,button,ipywidgets.HTML(
value="""<script>
var b=setInterval(a=>{
//Hopefully this is the first button
document.querySelector('#output-body button')?.click()},
500);
setTimeout(c=>clearInterval(b),1000*60*1);
//Stops clicking the button after 1 minute
</script>"""
),a)
I'm woring on a simple interface where I have a text box and an interactive plot.
The idea of the text box in to used for some logs.
I'm able to get the plot and update the text from the same function, but my issue is that the text box is to large, I need to change its width.
Below is my current code and a screenshot of the result:
from IPython.display import display
from ipywidgets import Button, Layout, Textarea, HBox, VBox
import numpy as np
from ipywidgets import interact, interactive, fixed, interact_manual
import plotly.graph_objects as go
def f(x):
x = np.arange(x)
t1.value = "".join([str(c) for c in x.tolist()] )
fig = go.Figure(data=go.Scatter(x=x, y=x**2))
fig.show()
x = np.linspace(10,100,1)
interactive_plot = interactive(f, x=(1, 20))
l = Layout(flex='0 1 auto', height='400px', min_height='40px', width='10')
t1 = Textarea(value='TA: height=40px', layout=l)
The layout attribute exposes CSS properties like height, width, border, margin of the ipywidgets. This allows one to control how the ipywidgets are laid out. It is possible to control the width and height of the textbox by tweaking the values in the Layout snippet below as required. The parameters accept values in pixels(px) or percentage of available space(%) or even 'auto' to allow these values to be set automatically. You can find an example in the docs. I've set the height as 30px and width as 'auto' to automatically set a width:
l = Layout(flex='0 1 auto', height='30px', min_height='40px', width='auto')
Output:
I am trying to turn off the spanselector after using it. When i go to the matplotlib documents, it says about this:
Set the visible attribute to False if you want to turn off the functionality of the span selector
But I do not know how to turn off the functionality of span selector like the documentation states.
Here is the code that I have.
def disconnect_span(self):
if self.selection_mode == None: #This is a variable that i use to call this method
self.SpanSelector(self.axes, self.onselect, "horizontal", useblit = True,
rectprops = dict(alpha= 0.5, facecolor = "blue"))
self.figure_canvas.draw_idle()
else:
#Here is where i want to put the disconnect condition
In order to toggle the visibility of the SpanSelector you will need to use the set_visible() method.
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
ax = plt.subplot(111)
ax.plot([1,2], [3,4])
def onselect(vmin, vmax):
print vmin, vmax
span = SpanSelector(ax, onselect, 'horizontal')
span.set_visible(False)
Here I have created it right after object creation, but you can call the set_visible() from anywhere to disable the selector as long you have the SpanSelector object.
I used matplotlib to create some plot, which depends on 8 variables. I would like to study how the plot changes when I change some of them. I created some script that calls the matplotlib one and generates different snapshots that later I convert into a movie, it is not bad, but a bit clumsy.
I wonder if somehow I could interact with the plot regeneration using keyboard keys to increase / decrease values of some of the variables and see instantly how the plot changes.
What is the best approach for this?
Also if you can point me to interesting links or a link with a plot example with just two sliders?
In addition to what #triplepoint mentioned, have a look at the slider widget.
There's an example on the matplotlib examples page. It's a graphical slider bar rather than keyboard bindings, but it works quite well for what you want to do.
Also note that to guarantee the sliders and buttons remain responsive and not garbage-collected, references to the objects (amp_slider, freq_slider, etc.) should be maintained by yourself.
(I'm making this community wiki, as I'm just copy-pasting from the example. This particular example teaches bad habits (e.g. from pylab import *), but it gets the point across. The example has been fixed to avoid the use of pylab.)
from numpy import pi, sin
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
def signal(amp, freq):
return amp * sin(2 * pi * freq * t)
axis_color = 'lightgoldenrodyellow'
fig = plt.figure()
ax = fig.add_subplot(111)
# Adjust the subplots region to leave some space for the sliders and buttons
fig.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
amp_0 = 5
freq_0 = 3
# Draw the initial plot
# The 'line' variable is used for modifying the line later
[line] = ax.plot(t, signal(amp_0, freq_0), linewidth=2, color='red')
ax.set_xlim([0, 1])
ax.set_ylim([-10, 10])
# Add two sliders for tweaking the parameters
# Define an axes area and draw a slider in it
amp_slider_ax = fig.add_axes([0.25, 0.15, 0.65, 0.03], facecolor=axis_color)
amp_slider = Slider(amp_slider_ax, 'Amp', 0.1, 10.0, valinit=amp_0)
# Draw another slider
freq_slider_ax = fig.add_axes([0.25, 0.1, 0.65, 0.03], facecolor=axis_color)
freq_slider = Slider(freq_slider_ax, 'Freq', 0.1, 30.0, valinit=freq_0)
# Define an action for modifying the line when any slider's value changes
def sliders_on_changed(val):
line.set_ydata(signal(amp_slider.val, freq_slider.val))
fig.canvas.draw_idle()
amp_slider.on_changed(sliders_on_changed)
freq_slider.on_changed(sliders_on_changed)
# Add a button for resetting the parameters
reset_button_ax = fig.add_axes([0.8, 0.025, 0.1, 0.04])
reset_button = Button(reset_button_ax, 'Reset', color=axis_color, hovercolor='0.975')
def reset_button_on_clicked(mouse_event):
freq_slider.reset()
amp_slider.reset()
reset_button.on_clicked(reset_button_on_clicked)
# Add a set of radio buttons for changing color
color_radios_ax = fig.add_axes([0.025, 0.5, 0.15, 0.15], facecolor=axis_color)
color_radios = RadioButtons(color_radios_ax, ('red', 'blue', 'green'), active=0)
def color_radios_on_clicked(label):
line.set_color(label)
fig.canvas.draw_idle()
color_radios.on_clicked(color_radios_on_clicked)
plt.show()
I followed the advice to check widgets in jupyter, and they work very well.
The example script is uploaded in GitHub https://github.com/LeonidBystrykh/course-python-for-beginners/blob/master/Interactive_dots.ipynb
from __future__ import print_function
from ipywidgets import interact, interactive, fixed, interact_manual
import ipywidgets as widgets
import matplotlib.pyplot as plt, random
def series(dots, colr):
a,b=[],[]
for i in range(dots):
a.append(random.randint(1,100))
b.append(random.randint(1,100))
plt.scatter(a,b, c=colr)
return()
interact(series, dots=(1,100,1), colr=["red","orange","brown"]);
The image copy is below
Matplotlib has some fairly nice gui functionality. There are some documentation examples in the source tarball of matplotlib, in /examples/user_interfaces and matplotlib>/examples/event_handling. Specifically on keyhandling is: http://matplotlib.sourceforge.net/examples/event_handling/keypress_demo.html
I have done something kind of similar to what you are aiming for:
import numpy as np
import pylab
class plotter:
def __init__(self, initial_values):
self.values
self.fig = pylab.figure()
pylab.gray()
self.ax = self.fig.add_subplot(111)
self.draw()
self.fig.canvas.mpl_connect('key_press_event',self.key)
def draw(self):
im = your_function(self.values)
pylab.show()
self.ax.imshow(im)
def key(self, event):
if event.key=='right':
self.values = modify()
elif event.key == 'left':
self.values = modify()
self.draw()
self.fig.canvas.draw()
I was using this to shift through displaying different images in a stack on keypresses, but you should be able to put logic in to modify your values given keyboard input.
If you want to do things like have the user input values, I think the examples have options for dialog boxes, but if you just want to increment/decrement a number of variables, just defining keyboard pairs for them in this manner might work well
For ipython or jupyter notebooks you can use ipywidgets:
from ipywidgets import *
def update(w=0,h=0):
print(h+w)
interact(update, w= widgets.IntSlider(value=1, min=0, max=7, step=1) ,
h= widgets.IntSlider(value=1, min=0, max=7, step=1) );
See documentation here:
https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html
Use waitforbuttonpress(timeout=0.001) then plot will see your mouse ticks.
You could also consider using the Quibbler package.
Quibbler automatically makes your normal matplotlib graphics interactive. There is no need to write callback functions.
See some examples in the Quibbler docs. In particular, see a specific example of a plot with two sliders.
from pyquibbler import iquib, initialize_quibbler
initialize_quibbler()
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
%matplotlib tk
# Figure setup:
fig = plt.figure(figsize=(4, 6))
axs = fig.add_axes([0, 0.36, 1, 0.64])
axs.axis('square')
axs.axis((-10, 10, -10, 10))
axs.axis('off')
axs_slider1 = fig.add_axes([0.3, 0.16, 0.55, 0.03])
axs_slider2 = fig.add_axes([0.3, 0.06, 0.55, 0.03])
# Defining quib input variables:
nPoints = iquib(120)
nCycles = iquib(30)
# Define downstream function quib calculations based on the quib inputs:
phi = np.linspace(0, 2 * np.pi * nCycles, nPoints)
r = np.linspace(0, 10, nPoints)
# Plot the data:
axs.plot(r * np.cos(phi), r * np.sin(phi), 'r-')
# Create quib-linked slider widgets:
Slider(ax=axs_slider1, label='nCycles', valmin=0, valmax=200, valstep=1, valinit=nCycles)
Slider(ax=axs_slider2, label='nPoints', valmin=0, valmax=200, valstep=1, valinit=nPoints)
For transparency: I am one of the developers of Quibbler.
I don't think that simply plotting graphs using plt.plot will allow you to do this. You will need to yourself make a custom GUI script/app by embedding Matplotlib into it. Currently, Matplotlib supports all the major GUI toolkits - PyGTK+, PyQt4 and wxPython.
I use wxPython and embedding matplotlib in it is fairly easy. Similar should be case with the other GUI toolkits. You can get all the information you need for this in the book -
It is available on amazon here.