Appropriate submit function for multiple textboxes - python

I am making a Matplotlib GUI where I want to interactively rescale the x and y axis. For this I want to use four textboxes where I can set the minimum and maximum of the x and y axis limits respectively. The code I have up until now is:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import TextBox
import matplotlib.gridspec as gridspec
fig, ax = plt.subplots()
ax.plot([1,3,2])
fig.subplots_adjust(bottom=0.4)
gs = gridspec.GridSpec(2,2)
gs.update(left=0.4, right=0.7, bottom=0.15, top=0.25, hspace=0.1)
axes = [fig.add_subplot(gs[i,j]) for i,j in [[0,0],[0,1],[1,0],[1,1]]]
# create the textboxes
tb_xmin = TextBox(axes[0],'x', hovercolor='0.975', label_pad=0.1)
tb_xmax = TextBox(axes[1],'', hovercolor='0.975')
tb_ymin = TextBox(axes[2],'y', hovercolor='0.975', label_pad=0.1)
tb_ymax = TextBox(axes[3],'', hovercolor='0.975')
def submit(val):
data = eval(val)
# how to know which limit to set here?
ax.set_xlim(data)
plt.draw()
for tb in [tb_xmin,tb_xmax,tb_ymin,tb_ymax]:
tb.on_submit(submit)
plt.show()
However I am stuck with the submit function. I want to have a function which recognises which textbox submits and input and sets the appropriate minimum or maximum value under the condition that the input is valid and within the datarange.
Can anyone help me out?

In order to update the limits via the TextBox inputs, you may pass some arguments together with the values to the function. A way to do this would be to create a lambda function, which has those arguments preset, depending on which limit to update.
def submit(val,func,pos):
data = float(val)
func([data,None][::pos])
tb_xmin.on_submit(lambda val: submit(val, ax.set_xlim, 1))
tb_xmax.on_submit(lambda val: submit(val, ax.set_xlim, -1))
tb_ymin.on_submit(lambda val: submit(val, ax.set_ylim, 1))
tb_ymax.on_submit(lambda val: submit(val, ax.set_ylim, -1))
Another option would be to always call the same function on submit and let the function gather the values from all 4 TextBoxes and set the limits simulataneously.
import matplotlib.pyplot as plt
from matplotlib.widgets import TextBox
import matplotlib.gridspec as gridspec
fig, ax = plt.subplots()
ax.plot([1,3,2])
fig.subplots_adjust(bottom=0.4)
gs = gridspec.GridSpec(2,2)
gs.update(left=0.4, right=0.7, bottom=0.15, top=0.25, hspace=0.1)
axes = [fig.add_subplot(gs[i,j]) for i,j in [[0,0],[0,1],[1,0],[1,1]]]
# create the textboxes
xlim = ax.get_xlim()
ylim = ax.get_ylim()
tb_xmin = TextBox(axes[0],'x', initial = str(xlim[0]), hovercolor='0.975', label_pad=0.1)
tb_xmax = TextBox(axes[1],'', initial = str(xlim[1]), hovercolor='0.975')
tb_ymin = TextBox(axes[2],'y', initial = str(ylim[0]), hovercolor='0.975', label_pad=0.1)
tb_ymax = TextBox(axes[3],'', initial = str(ylim[1]), hovercolor='0.975')
def submit(val):
lim = [float(tb.text) for tb in [tb_xmin,tb_xmax,tb_ymin,tb_ymax]]
ax.axis(lim)
fig.canvas.draw_idle()
for tb in [tb_xmin,tb_xmax,tb_ymin,tb_ymax]:
tb.on_submit(submit)
plt.show()

Related

gettting a colorbar programmatically from an axis object

Consider the following code in which data is being plotted within a loop. In order to not plot multiple colorbars, I remove them before plotting new ones. However, I am tracking these colorbars manually. Is there a way to get a reference to them using, say, something like ax[0].get_colorbar. That would make my code a lot simpler.
import matplotlib.pyplot as plt
import numpy as np
# How to get a colorbar from an axis?
nrows = 1
ncols = 2
nstep = 5
fig, ax = plt.subplots(nrows=nrows,ncols=ncols)
cb0 = None
cb1 = None
for istep in range(nstep):
data = np.random.random(size=(5,5))
imu0 = ax[0].pcolormesh(data)
imu1 = ax[1].pcolormesh(data)
# this code is for removing previously drawn colorbars
# I would like to get a reference to the colorbar cb0 from ax0
# and then remove it.
# I do not want to keep track of the colorbars manually
if cb0 is not None:
cb0.remove()
if cb1 is not None:
cb1.remove()
cb0 = plt.colorbar(imu0,ax=ax[0])
cb1 = plt.colorbar(imu1,ax=ax[1])
The following seems to do what I want:
import matplotlib.pyplot as plt
import numpy as np
# How to get a colorbar from an axis?
nrows = 1
ncols = 2
nstep = 10
fig, ax = plt.subplots(nrows=nrows,ncols=ncols)
cb0 = None
cb1 = None
for istep in range(nstep):
data = np.random.random(size=(5,5))+istep
imu0 = ax[0].pcolormesh(data)
imu1 = ax[1].pcolormesh(data)
if ax[0].collections[0].colorbar is None:
cb0 = plt.colorbar(imu0,ax=ax[0])
else:
ax[0].collections[0].colorbar.update_normal(imu0)
if ax[1].collections[0].colorbar is None:
cb1 = plt.colorbar(imu1,ax=ax[1])
else:
ax[1].collections[0].colorbar.update_normal(imu1)

Pycharm SciView truncate history

I am trying to create a program that can visualize the change of a portfolio in real time. To do this, I update my data and create a new plot with it. When I run the code below in PyCharm, SciView stops displaying the plots after 30 iterations. Ideally, I would like to have it only show the most recent plot, but it would also be fine if it just truncated the history so that I at least always see the current plot. Is there any way to do this? I tried different ways to close the figures (e. g. using plt.close()), but did not achieve the desired result.
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def create_plot(self):
"""
Takes an x and a y (both 1D arrays and constructs a plot from it)
:return: a pyplot figure object
"""
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Draw x and y lists
ax.clear()
ax.plot(self.x, self.y)
# Format plot
plt.xticks(rotation=90)
plt.title('Portfolio')
plt.ylabel('Value')
plt.show()
plt.close('all')
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.create_plot()
Rather than creating a new plot and window every time, you can also update the current Matplotlib figure data in each iteration. You then need to view the plot in an interactive Matplotlib environment.
Live updating Matplotlib plots
You can use code similar to this to update the data inside the plot:
import matplotlib.pyplot as plt
import random
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
# Variables for our updating data
x = []
y = []
for i in range(50):
# Generate random data
x.append(i)
y.append(random.random())
# Update the plot with the new x, y data
ax.plot(x, y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
Allow for interactive Matplotlib mode when using SciView
Deactivate SciView or manually set your backend to another interactive GUI to see the updating plot.
This code snipped automatically chooses the correct backend (same list as in the Matplotlib code):
import matplotlib.pyplot as plt
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
Applied to your code
Your code with suggested modifications would look like this:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def update_plot(self, fig, ax):
import _tkinter
try:
ax.plot(self.x, self.y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
# Capture an error in case the plotting window is being closed
except _tkinter.TclError:
pass
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
# Choose the right backend
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
# Create plot
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.update_plot(fig, ax) # Update the plot the new data
Same issue here.
The workaround I found is to change the matplotlib backend to plot outside the PyCharm.
import matplotlib
matplotlib.use('qt5Agg')
matplotlib.pyplot.ioff()
Then you have to explicit open a new figure and show
for i in range(100):
plt.figure()
...
...
plt.show()

How to plot a list of figures in a single subplot?

I have 2 lists of figures and their axes.
I need to plot each figure in a single subplot so that the figures become in one big subplot. How can I do that?
I tried for loop but it didn't work.
Here's what I have tried:
import ruptures as rpt
import matplotlib.pyplot as plt
# make random data with 100 samples and 9 columns
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
figs, axs = [], []
for i in range(signal.shape[1]):
points = signal[:,i]
# detection of change points
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
fig, ax = rpt.display(points, bkps, result, figsize=(15,3))
figs.append(fig)
axs.append(ax)
plt.show()
I had a look at the source code of ruptures.display(), and it accepts **kwargs that are passed on to matplotlib. This allows us to redirect the output to a single figure, and with gridspec, we can position individual subplots within this figure:
import ruptures as rpt
import matplotlib.pyplot as plt
n_samples, n_dims, sigma = 100, 9, 2
n_bkps = 4
signal, bkps = rpt.pw_constant(n_samples, n_dims, n_bkps, noise_std=sigma)
#number of subplots
n_subpl = signal.shape[1]
#give figure a name to refer to it later
fig = plt.figure(num = "ruptures_figure", figsize=(8, 15))
#define grid of nrows x ncols
gs = fig.add_gridspec(n_subpl, 1)
for i in range(n_subpl):
points = signal[:,i]
algo = rpt.Dynp(model='l2').fit(points)
result = algo.predict(n_bkps=2)
#rpt.display(points, bkps, result)
#plot into predefined figure
_, curr_ax = rpt.display(points, bkps, result, num="ruptures_figure")
#position current subplot within grid
curr_ax[0].set_position(gs[i].get_position(fig))
curr_ax[0].set_subplotspec(gs[i])
plt.show()
Sample output:

matplotlib display only one graph of a set of 10 like a slideshow

I have a set of 10 graphs. (based on X/Y-pairs) (In this example only 3)
Displaying one graph is easy, same to all graphs in the same window.(See picture)
But I haven't found a solution for what I want :
The 10 graphs are Data from a spectrum analyzer and are showing a signal.
I want to display the first graph, delete or remove it and display the 2nd graph in the same window.
Then next, the second graph will be removed and the 3rd shall be seen (and so on)
Thats my Code :
from matplotlib import pyplot as plt
import numpy as np
datei = ['./2450ATT0.csv','./2450ATT0-1.csv','./2450ATT0-2.csv']
for file in datei:
x = np.genfromtxt(file, usecols =(0), delimiter=';', unpack=True)
y = np.genfromtxt(file, usecols =(1), delimiter=';', unpack=True, dtype=float)
plt.xlim(2435,2465)
plt.ylim(-120,-20)
plt.xlabel('Frequenz')
plt.ylabel('Leistung')
plt.plot(x/1000000,y, label=file)
plt.show()
Do you have any idea ?
I also have had a look at plt.animate. but I haven't found a solution with that suggestions.
Thank you.
Andi
Showing the data one after the other seems a bit unergonomic to me. Also using an animation might not be the best solution. What if after inspection of the second dataset you want to go back to the first?
I would therefore implement a solution which allows to go back and forth between the spectra.
The following sandbox example is based on a solution I have provided to a similar problem with images. It uses a discrete Slider to walk through the pages. Although it may seem a bit complicated on first sight, you do not really have to understand the PageSlider class in order to use it. Just look at the code down in the __main__ part.
import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1
class PageSlider(matplotlib.widgets.Slider):
def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d',
closedmin=True, closedmax=True,
dragging=True, **kwargs):
self.facecolor=kwargs.get('facecolor',"w")
self.activecolor = kwargs.pop('activecolor',"b")
self.fontsize = kwargs.pop('fontsize', 10)
self.numpages = numpages
super(PageSlider, self).__init__(ax, label, 0, numpages,
valinit=valinit, valfmt=valfmt, **kwargs)
self.poly.set_visible(False)
self.vline.set_visible(False)
self.pageRects = []
for i in range(numpages):
facecolor = self.activecolor if i==valinit else self.facecolor
r = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1,
transform=ax.transAxes, facecolor=facecolor)
ax.add_artist(r)
self.pageRects.append(r)
ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),
ha="center", va="center", transform=ax.transAxes,
fontsize=self.fontsize)
self.valtext.set_visible(False)
divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
bax = divider.append_axes("right", size="5%", pad=0.05)
fax = divider.append_axes("right", size="5%", pad=0.05)
self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_back.label.set_fontsize(self.fontsize)
self.button_forward.label.set_fontsize(self.fontsize)
self.button_back.on_clicked(self.backward)
self.button_forward.on_clicked(self.forward)
def _update(self, event):
super(PageSlider, self)._update(event)
i = int(self.val)
if i >=self.valmax:
return
self._colorize(i)
def _colorize(self, i):
for j in range(self.numpages):
self.pageRects[j].set_facecolor(self.facecolor)
self.pageRects[i].set_facecolor(self.activecolor)
def forward(self, event):
current_i = int(self.val)
i = current_i+1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
def backward(self, event):
current_i = int(self.val)
i = current_i-1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
if __name__ == "__main__":
import numpy as np
from matplotlib import pyplot as plt
num_pages = 10
data = np.random.rand(700, num_pages)
spec = np.linspace(-10,10, 700)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
ax.set_ylim([0.,1.6])
line, = ax.plot(spec,data[:,0], color="b")
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")
def update(val):
i = int(slider.val)
line.set_ydata(data[:,i])
slider.on_changed(update)
plt.show()
The code above is working and shows how this would look like. In your specific case, you would need to change it a bit.
I tried to adapt your code accordingly, but of course I cannot guarantee that it works. This code has to be put below the __main__ part, the PageSlider must stay unchanged.
import numpy as np
from matplotlib import pyplot as plt
dateien = ['./2450ATT0.csv','./2450ATT0-1.csv','./2450ATT0-2.csv']
data_x = []
data_y = []
for datei in dateien: #do not call a variable "file" in python as this is protected
x = np.genfromtxt(datei, usecols =(0), delimiter=';', unpack=True)
x = x/1000000.
y = np.genfromtxt(datei, usecols =(1), delimiter=';', unpack=True, dtype=float)
data_x.append(x)
data_y.append(y)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
ax.set_xlim([2435,2465])
ax.set_xlim([-120,20])
ax.set_xlabel('Frequenz')
ax.set_ylabel('Leistung')
text = ax.text(0.98,0.98, dateien[0], ha="right", va="top")
line, = ax.plot(data_x[0],data_y[0], color="b")
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', len(dateien), activecolor="orange")
def update(val):
i = int(slider.val)
line.set_data(data_x[i],data_y[i])
text.set_text(dateien[i])
slider.on_changed(update)
plt.show()
Edit:
For a simple animation, you would rather use matplotlib.animation.FuncAnimation and the code would look something along those lines
import numpy as np
from matplotlib import pyplot as plt
dateien = ['./2450ATT0.csv','./2450ATT0-1.csv','./2450ATT0-2.csv']
data_x = []
data_y = []
for datei in dateien: # do not call a variable "file" in python, this is a protected word
x = np.genfromtxt(datei, usecols =(0), delimiter=';', unpack=True)
x = x/1000000.
y = np.genfromtxt(datei, usecols =(1), delimiter=';', unpack=True, dtype=float)
data_x.append(x)
data_y.append(y)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
ax.set_xlim([2435,2465])
ax.set_xlim([-120,20])
ax.set_xlabel('Frequenz')
ax.set_ylabel('Leistung')
line, = ax.plot(data_x[0],data_y[0], color="b")
def update(i):
line.set_data(data_x[i],data_y[i])
ani = matplotlib.animation.FuncAnimation(fig, update,
frames= len(dateien), interval = 200, blit = False, repeat= True)
plt.show()
I like to suggest using multiple subplots in a 2D matrix layout and animating them. Examples (w/o animation) can be seen from http://matplotlib.org/examples/pylab_examples/subplots_demo.html and https://www.dataquest.io/blog/images/python_r/python_pairs.png.
In this way your students get to see changes in all the data simultaneously. The implementation details for the subplots are given in the 1st example. Furas has directed you to the plot animation example.

How to assign a plot to a variable and use the variable as the return value in a Python function

I am creating two Python scripts to produce some plots for a technical report. In the first script I am defining functions that produce plots from raw data on my hard-disk. Each function produces one specific kind of plot that I need. The second script is more like a batch file which is supposed to loop around those functions and store the produced plots on my hard-disk.
What I need is a way to return a plot in Python. So basically I want to do this:
fig = some_function_that_returns_a_plot(args)
fig.savefig('plot_name')
But what I do not know is how to make a plot a variable that I can return. Is this possible? Is so, how?
You can define your plotting functions like
import numpy as np
import matplotlib.pyplot as plt
# an example graph type
def fig_barh(ylabels, xvalues, title=''):
# create a new figure
fig = plt.figure()
# plot to it
yvalues = 0.1 + np.arange(len(ylabels))
plt.barh(yvalues, xvalues, figure=fig)
yvalues += 0.4
plt.yticks(yvalues, ylabels, figure=fig)
if title:
plt.title(title, figure=fig)
# return it
return fig
then use them like
from matplotlib.backends.backend_pdf import PdfPages
def write_pdf(fname, figures):
doc = PdfPages(fname)
for fig in figures:
fig.savefig(doc, format='pdf')
doc.close()
def main():
a = fig_barh(['a','b','c'], [1, 2, 3], 'Test #1')
b = fig_barh(['x','y','z'], [5, 3, 1], 'Test #2')
write_pdf('test.pdf', [a, b])
if __name__=="__main__":
main()
If you don't want the picture to be displayed and only get a variable in return, then you can try the following (with some additional stuff to remove axis):
def myplot(t,x):
fig = Figure(figsize=(2,1), dpi=80)
canvas = FigureCanvasAgg(fig)
ax = fig.add_subplot()
ax.fill_between(t,x)
ax.autoscale(tight=True)
ax.axis('off')
canvas.draw()
buf = canvas.buffer_rgba()
X = np.asarray(buf)
return X
The returned variable X can be used with OpenCV for example and do a
cv2.imshow('',X)
These import must be included:
from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvasAgg
The currently accepted answer didn't work for me as such, as I was using scipy.stats.probplot() to plot. I used matplotlib.pyplot.gca() to access an Axes instance directly instead:
"""
For my plotting ideas, see:
https://pythonfordatascience.org/independent-t-test-python/
For the dataset, see:
https://github.com/Opensourcefordatascience/Data-sets
"""
# Import modules.
from scipy import stats
import matplotlib.pyplot as plt
import pandas as pd
from tempfile import gettempdir
from os import path
from slugify import slugify
# Define plot func.
def get_plots(df):
# plt.figure(): Create a new P-P plot. If we're inside a loop, and want
# a new plot for every iteration, this is important!
plt.figure()
stats.probplot(diff, plot=plt)
plt.title('Sepal Width P-P Plot')
pp_p = plt.gca() # Assign an Axes instance of the plot.
# Plot histogram. This uses pandas.DataFrame.plot(), which returns
# an instance of the Axes directly.
hist_p = df.plot(kind = 'hist', title = 'Sepal Width Histogram Plot',
figure=plt.figure()) # Create a new plot again.
return pp_p, hist_p
# Import raw data.
df = pd.read_csv('https://raw.githubusercontent.com/'
'Opensourcefordatascience/Data-sets/master//Iris_Data.csv')
# Subset the dataset.
setosa = df[(df['species'] == 'Iris-setosa')]
setosa.reset_index(inplace= True)
versicolor = df[(df['species'] == 'Iris-versicolor')]
versicolor.reset_index(inplace= True)
# Calculate a variable for analysis.
diff = setosa['sepal_width'] - versicolor['sepal_width']
# Create plots, save each of them to a temp file, and show them afterwards.
# As they're just Axes instances, we need to call get_figure() at first.
for plot in get_plots(diff):
outfn = path.join(gettempdir(), slugify(plot.title.get_text()) + '.png')
print('Saving a plot to "' + outfn + '".')
plot.get_figure().savefig(outfn)
plot.get_figure().show()

Categories

Resources