Checking which item was select in CheckButtons [duplicate] - python-3.x

I have two CheckButtons widgets with 3 elements each. I'd like to read the status of both widgets when either one of the CheckButtons is selected then update the chart accordingly.
The slider widget has a .val for returning the status of a slider, but the CheckButtons widget seems a bit more awkward (or am I missing something obvious)?
short example:
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
class Example:
def updateChart(self, event):
colour = self.colours.labels # gets labes as text object, is there an easy way of getting the status?
print colour
# measurement = measurements.something
def __init__(self):
colourax = plt.axes([0.5, 0.4, 0.09, 0.2])
measurementax = plt.axes([0.5, 0.6, 0.09, 0.2])
self.colours = CheckButtons(colourax, ('Red', 'Green', 'Blue'), (False, False, False))
self.measurements = CheckButtons(measurementax, ('1', '2', '3'), (False, False, False))
self.colours.on_clicked(self.updateChart)
self.measurements.on_clicked(self.updateChart)
def run(self):
plt.show()
ex = Example()
ex.run()

I know it's a bit awkward, but you can check for visibility of on of the cross lines in check boxes.
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
colourax = plt.axes([0.5, 0.4, 0.09, 0.2])
colours = CheckButtons(colourax, ('Red', 'Green', 'Blue'), (False, False, False))
isRedChecked = colours.lines[0][0].get_visible()
isGreenChecked = colours.lines[1][0].get_visible()
isBlueChecked = colours.lines[2][0].get_visible()

The current development version (as of July 2017) has a
CheckButtons.get_status()
method incorporated. This can be used to query the current status of the checkboxes. It should be released in the stable version pretty soon. (Source here)
Until then, you may emulate this behaviour by using your own get_status method as in the following. It uses the same mechanism as the get_status() method from the development version, which is also very close to what the answer of #Gruby is proposing (looking at the visibility of the lines).
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
class Example:
def updateChart(self, event):
colour = self.get_status(self.colours)
measurement = self.get_status(self.measurements)
print measurement, colour
def get_status(self, cb):
return [l1.get_visible() for (l1, l2) in cb.lines]
def __init__(self):
colourax = plt.axes([0.5, 0.4, 0.09, 0.2])
measurementax = plt.axes([0.5, 0.6, 0.09, 0.2])
self.colours = CheckButtons(colourax, ('Red', 'Green', 'Blue'), (False, False, False))
self.measurements = CheckButtons(measurementax, ('1', '2', '3'), (False, False, False))
self.colours.on_clicked(self.updateChart)
self.measurements.on_clicked(self.updateChart)
def run(self):
plt.show()
ex = Example()
ex.run()

There might perhaps be a more elegant way but you can always keep track of the states of each of the checkboxes yourself, e.g. in a dict. The function that you specify using on_clicked() will receive the label string of the active checkbox as its second argument, which you can then use to update the status appropriately:
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
class Example:
def onColor(self,label):
self.cstates[label] = not self.cstates[label]
print 'un'*(not self.cstates[label]) + 'checked %s' %label
self.updateChart()
def onMeasurement(self,label):
self.mstates[label] = not self.mstates[label]
print 'un'*(not self.mstates[label]) + 'checked %s' %label
self.updateChart()
def updateChart(self, event=None):
"""do something here using self.cstates and self.mstates?"""
pass
def __init__(self):
colourax = plt.axes([0.5, 0.4, 0.09, 0.2])
measurementax = plt.axes([0.5, 0.6, 0.09, 0.2])
clabels, cvals = ('Red', 'Green', 'Blue'), (False,)*3
mlabels, mvals = ('1', '2', '3'), (False,)*3
self.cstates = dict(zip(clabels,cvals))
self.mstates = dict(zip(mlabels,mvals))
self.colours = CheckButtons(colourax, clabels, cvals)
self.colours.on_clicked(self.onColor)
self.measurements = CheckButtons(measurementax, mlabels, mvals)
self.measurements.on_clicked(self.onMeasurement)
def run(self):
plt.show()
ex = Example()
ex.run()
Not the prettiest, but it works!

Related

Order of placement of widgets in PyQt: Can I place a GraphicalLayoutWidget on a QHBoxLayout?

I'm new to PyQt and pyqtgraph and have a heatplot, which I'd like to place on a widget (if that's the right term), adjacent to which a slider will eventually appear. The code I have thus far, which is a modification of something I've shamelessly copied from an online tutorial, is as follows:
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets, mkQApp
from PyQt5.QtWidgets import QHBoxLayout
from pyqtgraph.Qt import QtGui, QtCore
pg.setConfigOption('background', 'lightgray')
pg.setConfigOption('foreground','black')
font = QtGui.QFont("Times", 18)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QHBoxLayout()
graph_widget = pg.GraphicsLayoutWidget(show=True)
self.setCentralWidget(graph_widget)
self.setWindowTitle('pyqtgraph example: heatmap display')
self.setStyleSheet("background-color: lightgray;")
self.resize(1000,1000)
#layout.addWidget(graph_widget) # Where my error occurs
self.show()
corrMatrix = np.array([
[ 1. , 0.5184571 , -0.70188642],
[ 0.5184571 , 1. , -0.86094096],
[-0.70188642, -0.86094096, 1. ]
])
columns = ["A", "B", "C"]
pg.setConfigOption('imageAxisOrder', 'row-major')
correlogram = pg.ImageItem()
tr = QtGui.QTransform().translate(-0.5, -0.5)
correlogram.setTransform(tr)
correlogram.setImage(corrMatrix)
plotItem = graph_widget.addPlot()
plotItem.invertY(True)
plotItem.setDefaultPadding(0.0)
plotItem.addItem(correlogram)
plotItem.showAxes( True, showValues=(True, True, False, False), size=40 )
ticks = [ (idx, label) for idx, label in enumerate( columns ) ]
for side in ('left','top','right','bottom'):
plotItem.getAxis(side).setTicks( (ticks, []) )
plotItem.getAxis(side).setTickFont(font)
plotItem.getAxis('bottom').setHeight(10)
colorMap = pg.colormap.get("CET-D1")
bar = pg.ColorBarItem( interactive=False,values=(0,1), colorMap=colorMap)
bar.setImageItem(correlogram, insert_in=plotItem)
mkQApp("Correlation matrix display")
main_window = MainWindow()
if __name__ == '__main__':
pg.exec()
The result is shown below:
Eventually I would like to place the above in a layout, in which a row contains my plot, a slider (and a few other widgets). A TypeError message results when I un-comment the line layout.addWidget(graph_widget). The message states
TypeError: addWidget(self, QWidget, stretch: int = 0, alignment: Union[Qt.Alignment,
Qt.AlignmentFlag] = Qt.Alignment()): argument 1 has unexpected type
'GraphicsLayoutWidget'
Is it not possible to place a GraphicsLayoutWidget on a QHBoxLayout()? If so, what's the correct way to organize things so I have my graph adjacent to which I can place sliders, line edits, etc.
One issue is that you are setting your graph_widget as the central widget for your QMainWindow instance and then later adding it to a layout with the intention of adding more widgets.
I think you are more likely to achieve the results you are looking for if you set a generic QWidget as the window's central widget, set a layout, and then add the graph_widget and any other widgets to the layout.
Here is an example using your code and the solution from #musicmante in the comments, and adding a vertical slider:
from PyQt5.QtWidgets import QHBoxLayout, QWidget, QSlider # Import PyQt5 first before pyqtgraph
from pyqtgraph.Qt import QtWidgets, mkQApp
import pyqtgraph as pg
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
pg.setConfigOption('background', 'lightgray')
pg.setConfigOption('foreground','black')
font = QtGui.QFont("Times", 18)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.central = QWidget() # create a QWidget
slider = QSlider(orientation=QtCore.Qt.Vertical) # Vertical Slider
graph_widget = pg.GraphicsLayoutWidget(show=True)
self.setWindowTitle('pyqtgraph example: heatmap display')
self.setStyleSheet("background-color: lightgray;")
self.resize(1000,1000)
self.setCentralWidget(self.central) # set the QWidget as centralWidget
layout = QHBoxLayout(self.central) # assign layout to central widget
layout.addWidget(graph_widget) # No More error
layout.addWidget(slider) # add a slider
self.show()
corrMatrix = np.array([
[ 1. , 0.5184571 , -0.70188642],
[ 0.5184571 , 1. , -0.86094096],
[-0.70188642, -0.86094096, 1. ]
])
columns = ["A", "B", "C"]
pg.setConfigOption('imageAxisOrder', 'row-major')
correlogram = pg.ImageItem()
tr = QtGui.QTransform().translate(-0.5, -0.5)
correlogram.setTransform(tr)
correlogram.setImage(corrMatrix)
plotItem = graph_widget.addPlot()
plotItem.invertY(True)
plotItem.setDefaultPadding(0.0)
plotItem.addItem(correlogram)
plotItem.showAxes( True, showValues=(True, True, False, False), size=40 )
ticks = [ (idx, label) for idx, label in enumerate( columns ) ]
for side in ('left','top','right','bottom'):
plotItem.getAxis(side).setTicks( (ticks, []) )
plotItem.getAxis(side).setTickFont(font)
plotItem.getAxis('bottom').setHeight(10)
colorMap = pg.colormap.get("CET-D1")
bar = pg.ColorBarItem( interactive=False,values=(0,1), colorMap=colorMap)
bar.setImageItem(correlogram, insert_in=plotItem)
mkQApp("Correlation matrix display")
main_window = MainWindow()
if __name__ == '__main__':
pg.exec()

How to connect matplotlib cursor mouse_move object with slider value?

I have a figure where 2 axhlines move with mouse movement. I want to put a slider at the bottom where it will change the range of y-axis values covered by these axhlines.
enter image description here
I tried the following code. Problem is that the value of the slider changes but the mouse event object does not update.
Thanks
%matplotlib notebook
import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib.widgets import Slider
np.random.seed(12345)
df = pd.DataFrame([np.random.normal(32000,200000,3650),
np.random.normal(43000,100000,3650),
np.random.normal(43500,140000,3650),
np.random.normal(48000,70000,3650)],
index=[1992,1993,1994,1995])
df=df.T
data=df.describe().T
data['error']=df.sem()
data['error_range']=df.sem()*1.96
fig, ax = plt.subplots()
def plot_bar(x,y,error,title,alpha_level=0.7):
ax.bar(x,y, yerr=error,
align='center', alpha=alpha_level,
error_kw=dict(ecolor='black', elinewidth=1, capsize=5, capthick=1))
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_title(title)
ax.xaxis.set_major_locator(ticker.FixedLocator(data.index))
ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
ax.set_ylim([-5000,55000])
ax.set_xlim([1990.5,1995.5])
ax.spines['bottom'].set_position(('data',0))
ax.spines['left'].set_position(('data',1991.4))
plt.tight_layout()
return (ax, ax.get_children()[1:5])
ax, barlist=plot_bar(x=data.index,y=data['mean'],error=data['error_range'],title='Even Harder Option', alpha_level=0.6)
fig.subplots_adjust(bottom=0.1)
axcolor = 'lightgoldenrodyellow'
range_slider = plt.axes([0.2, 0.05, 0.65, 0.03], facecolor=axcolor)
slider = Slider(range_slider, 'Range', 0, 55000, valinit=10000, valstep=100)
def update(val):
slider.val = slider.val
slider.on_changed(update)
class Cursor(object):
_df=None
_bl=None
def __init__(self, ax,data_F, bars, slider):
#global slider
self._df=data_F
self._bl=bars
self.ax = ax
self.lx1 = ax.axhline(color='b')
self.lx2 = ax.axhline(color='b')
self.text1 = ax.text(1990.55, y, '%d' %45,bbox=dict(fc='white',ec='k'), fontsize='x-small')
self.text2 = ax.text(1990.55, y, '%d' %45,bbox=dict(fc='white',ec='k'), fontsize='x-small')
self._sl = slider.val
def mouse_move(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
r = self._sl
y1 , y2 = y+r/2 , y-r/2
#self.lx1.set_ydata(y)
self.lx1.set_ydata(y+r/2)
self.lx2.set_ydata(y-r/2)
for i in range(4):
#shade = cmap(norm((data['mean'.values[i]-event.ydata)/df_std.values[i]))
prob1=stats.norm.cdf(y1,self._df['mean'].values[i],self._df['error'].values[i])
prob2=stats.norm.cdf(y2,self._df['mean'].values[i],self._df['error'].values[i])
shade = cmap(prob1-prob2)
self._bl[i].set_color(shade)
self.text1.set_text('%d' %y1)
self.text1.set_position((1990.55, y1))
self.text2.set_text('%d' %y2)
self.text2.set_position((1990.55, y2))
plt.draw()
cursor = Cursor(ax, data,barlist, slider)
#plt.connect('range_change', cursor.update)
plt.connect('motion_notify_event', cursor.mouse_move)

How to insert an interactive matplotlib graph into a tkinter canvas

I am trying to place an interactive matplotlib graph (one that has Sliders, resetbutton, and Radiobuttons) into a tkinter Canvas. I have succeded in adding a noninteractive graph but cannot find the issues when it becomes interactive.
I have tried changing everything to using matplotlib Figure instead of pyplot with no luck.
As the code is currently written, both classes work but when line 59 is changed from graph = FigureCanvasTkAgg(self.random_graph(),self.graph_tab) to graph = FigureCanvasTkAgg(EllipseSlider(),self.graph_tab) and plt.show() is removed, the code no longer works.
Any help would be great. The code is below.
import tkinter as tk
from tkinter.ttk import Notebook
from tkinter import Canvas
from tkinter import messagebox as msg
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
#----------------------------------------------------------
class LukeOutline(tk.Tk):
#------------------------------------------------------
def __init__(self):
# Inherit from tk.Tk
super().__init__()
# Title and size of the window
self.title('Luke Outline')
self.geometry('600x400')
# Create the drop down menus
self.menu = tk.Menu(self,bg='lightgrey',fg='black')
self.file_menu = tk.Menu(self.menu,tearoff=0,bg='lightgrey',fg='black')
self.file_menu.add_command(label='Add Project',command=self.unfinished)
self.file_menu.add_command(label='Quit',command=self.quit)
self.menu.add_cascade(label='File',menu=self.file_menu)
self.config(menu=self.menu)
# Create the tabs (Graph, File Explorer, etc.)
self.notebook = Notebook(self)
graph_tab = tk.Frame(self.notebook)
file_explorer_tab = tk.Frame(self.notebook)
# Sets the Graph Tab as a Canvas where figures, images, etc. can be added
self.graph_tab = tk.Canvas(graph_tab)
self.graph_tab.pack(side=tk.TOP, expand=1)
# Sets the file explorer tab as a text box (change later)
self.file_explorer_tab = tk.Text(file_explorer_tab,bg='white',fg='black')
self.file_explorer_tab.pack(side=tk.TOP, expand=1)
# Add the tabs to the GUI
self.notebook.add(graph_tab, text='Graph')
self.notebook.add(file_explorer_tab, text='Files')
self.notebook.pack(fill=tk.BOTH, expand=1)
# Add the graph to the graph tab
#graph = FigureCanvasTkAgg(self.random_graph(),self.graph_tab)
graph = FigureCanvasTkAgg(self.random_graph(),self.graph_tab)
graph.get_tk_widget().pack(side='top',fill='both',expand=True)
graph.draw()
#------------------------------------------------------
def quit(self):
'''
Quit the program
'''
self.destroy()
#------------------------------------------------------
def unfinished(self):
'''
Messagebox for unfinished items
'''
msg.showinfo('Unfinished','This feature has not been finished')
#------------------------------------------------------
def random_graph(self):
x = list(range(0,10))
y = [i**3 for i in x]
fig = Figure()
axes = fig.add_subplot(111)
axes.plot(x,y,label=r'$x^3$')
axes.legend()
return fig
#----------------------------------------------------------
class EllipseSlider():
#------------------------------------------------------
def __init__(self):
# Initial values
self.u = 0. #x-position of the center
self.v = 0. #y-position of the center
self.a = 2. #radius on the x-axis
self.b = 1.5 #radius on the y-axis
# Points to plot against
self.t = np.linspace(0, 2*np.pi, 100)
# Set up figure with centered axes and grid
self.fig, self.ax = plt.subplots()
self.ax.set_aspect(aspect='equal')
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
self.ax.xaxis.set_ticks_position('bottom')
self.ax.yaxis.set_ticks_position('left')
self.ax.set_xlim(-2,2)
self.ax.set_ylim(-2,2)
plt.grid(color='lightgray',linestyle='--')
# Initial plot
self.l, = self.ax.plot(self.u+self.a*np.cos(self.t),
self.v+self.b*np.sin(self.t),'k')
# Slider setup
self.axcolor = 'lightgoldenrodyellow'
self.axb = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=self.axcolor)
self.axa = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=self.axcolor)
self.sb = Slider(self.axb, 'Y Radius', 0.1, 2.0, valinit=self.b)
self.sa = Slider(self.axa, 'X Radius', 0.1, 2.0, valinit=self.a)
# Call update as slider is changed
self.sb.on_changed(self.update)
self.sa.on_changed(self.update)
# Reset if reset button is pushed
self.resetax = plt.axes([0.8,0.025,0.1,0.04])
self.button = Button(self.resetax, 'Reset', color=self.axcolor, hovercolor='0.975')
self.button.on_clicked(self.reset)
# Color button setup
self.rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=self.axcolor)
self.radio = RadioButtons(self.rax, ('red', 'blue', 'green'), active=0)
self.radio.on_clicked(self.colorfunc)
# Show the plot
plt.show()
#------------------------------------------------------
def update(self, val):
'''
Updates the plot as sliders are moved
'''
self.a = self.sa.val
self.b = self.sb.val
self.l.set_xdata(self.u+self.a*np.cos(self.t))
self.l.set_ydata(self.u+self.b*np.sin(self.t))
#------------------------------------------------------
def reset(self, event):
'''
Resets everything if reset button clicked
'''
self.sb.reset()
self.sa.reset()
#------------------------------------------------------
def colorfunc(self, label):
'''
Changes color of the plot when button clicked
'''
self.l.set_color(label)
self.fig.canvas.draw_idle()
#----------------------------------------------------------
if __name__ == '__main__':
#luke_gui = LukeOutline()
#luke_gui.mainloop()
es = EllipseSlider()
I figured it out. All that needs to be done is that a Figure needs to be declared before the FigureCanvasTkAgg line and then the graph can be created after with passing fig to it. Also, I needed to change all calls to pyplot to things that worked with Figure.
The code which works is below.
import tkinter as tk
from tkinter.ttk import Notebook
from tkinter import Canvas
from tkinter import messagebox as msg
import numpy as np
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.widgets import Slider, Button, RadioButtons
#----------------------------------------------------------
class LukeOutline(tk.Tk):
#------------------------------------------------------
def __init__(self):
# Inherit from tk.Tk
super().__init__()
# Title and size of the window
self.title('Luke Outline')
self.geometry('600x400')
# Create the drop down menus
self.menu = tk.Menu(self,bg='lightgrey',fg='black')
self.file_menu = tk.Menu(self.menu,tearoff=0,bg='lightgrey',fg='black')
self.file_menu.add_command(label='Add Project',command=self.unfinished)
self.file_menu.add_command(label='Quit',command=self.quit)
self.menu.add_cascade(label='File',menu=self.file_menu)
self.config(menu=self.menu)
# Create the tabs (Graph, File Explorer, etc.)
self.notebook = Notebook(self)
graph_tab = tk.Frame(self.notebook)
file_explorer_tab = tk.Frame(self.notebook)
# Sets the Graph Tab as a Canvas where figures, images, etc. can be added
self.graph_tab = tk.Canvas(graph_tab)
self.graph_tab.pack(side=tk.TOP, expand=1)
# Sets the file explorer tab as a text box (change later)
self.file_explorer_tab = tk.Text(file_explorer_tab,bg='white',fg='black')
self.file_explorer_tab.pack(side=tk.TOP, expand=1)
# Add the tabs to the GUI
self.notebook.add(graph_tab, text='Graph')
self.notebook.add(file_explorer_tab, text='Files')
self.notebook.pack(fill=tk.BOTH, expand=1)
# Add the graph to the graph tab
self.fig = Figure()
graph = FigureCanvasTkAgg(self.fig,self.graph_tab)
graph.get_tk_widget().pack(side='top',fill='both',expand=True)
EllipseSlider(self.fig)
#------------------------------------------------------
def quit(self):
'''
Quit the program
'''
self.destroy()
#------------------------------------------------------
def unfinished(self):
'''
Messagebox for unfinished items
'''
msg.showinfo('Unfinished','This feature has not been finished')
#------------------------------------------------------
def random_graph(self):
x = list(range(0,10))
y = [i**3 for i in x]
fig = Figure()
axes = fig.add_subplot(111)
axes.plot(x,y,label=r'$x^3$')
axes.legend()
return fig
#----------------------------------------------------------
class EllipseSlider():
#------------------------------------------------------
def __init__(self,fig):
self.fig = fig
# Initial values
self.u = 0. #x-position of the center
self.v = 0. #y-position of the center
self.a = 2. #radius on the x-axis
self.b = 1.5 #radius on the y-axis
# Points to plot against
self.t = np.linspace(0, 2*np.pi, 100)
# Set up figure with centered axes and grid
self.ax = self.fig.add_subplot(111)
self.ax.set_aspect(aspect='equal')
self.ax.spines['left'].set_position('center')
self.ax.spines['bottom'].set_position('center')
self.ax.spines['right'].set_color('none')
self.ax.spines['top'].set_color('none')
self.ax.xaxis.set_ticks_position('bottom')
self.ax.yaxis.set_ticks_position('left')
self.ax.set_xlim(-2,2)
self.ax.set_ylim(-2,2)
self.ax.grid(color='lightgray',linestyle='--')
# Initial plot
self.l, = self.ax.plot(self.u+self.a*np.cos(self.t),
self.v+self.b*np.sin(self.t),'k')
# Slider setup
self.axcolor = 'lightgoldenrodyellow'
self.axb = self.fig.add_axes([0.25, 0.1, 0.65, 0.03], facecolor=self.axcolor)
self.axa = self.fig.add_axes([0.25, 0.15, 0.65, 0.03], facecolor=self.axcolor)
self.sb = Slider(self.axb, 'Y Radius', 0.1, 2.0, valinit=self.b)
self.sa = Slider(self.axa, 'X Radius', 0.1, 2.0, valinit=self.a)
# Call update as slider is changed
self.sb.on_changed(self.update)
self.sa.on_changed(self.update)
# Reset if reset button is pushed
self.resetax = self.fig.add_axes([0.8,0.025,0.1,0.04])
self.button = Button(self.resetax, 'Reset', color=self.axcolor, hovercolor='0.975')
self.button.on_clicked(self.reset)
# Color button setup
self.rax = self.fig.add_axes([0.025, 0.5, 0.15, 0.15], facecolor=self.axcolor)
self.radio = RadioButtons(self.rax, ('red', 'blue', 'green'), active=0)
self.radio.on_clicked(self.colorfunc)
#------------------------------------------------------
def update(self, val):
'''
Updates the plot as sliders are moved
'''
self.a = self.sa.val
self.b = self.sb.val
self.l.set_xdata(self.u+self.a*np.cos(self.t))
self.l.set_ydata(self.u+self.b*np.sin(self.t))
#------------------------------------------------------
def reset(self, event):
'''
Resets everything if reset button clicked
'''
self.sb.reset()
self.sa.reset()
#------------------------------------------------------
def colorfunc(self, label):
'''
Changes color of the plot when button clicked
'''
self.l.set_color(label)
self.fig.canvas.draw_idle()
#----------------------------------------------------------
if __name__ == '__main__':
luke_gui = LukeOutline()
luke_gui.mainloop()
'''

QWidget raise above matplotlib canvas

I am working on a project on which I have a GUI (coded by hand) with two tabs, and on each tab I have a different canvas (to plot different things in each tabs).
But, I added also some widgets on these tabs and when I add them to the layout, if I add the canvas at the same position of a button in the layout for example, I can click on this button anymore.
I know on PyQt it is possible to raise the level of the widget, so is there a way to do the same thing with a canvas?
Thank you in advance for your help. On this example, the "Quit" is active only on the right half.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
class FenetrePrincipale(QWidget):
def __init__(self, parent=None):
super(FenetrePrincipale, self).__init__(parent)
self.setupUi(self)
# Fonction de configuration de la classe
def setupUi(self, Form):
self.Form = Form
Form.setMinimumSize(1220, 850)
self.creation_GUI()
self.creation_figure()
self.creation_layout()
self.tabWidget.setCurrentIndex(0)
self.Bouton_quitter.clicked.connect(self.close)
def resizeEvent(self, QResizeEvent):
self.tabWidget.setMinimumSize(QSize(self.width() - 20, self.height() - 60))
def creation_GUI(self):
self.tabWidget = QTabWidget()
self.tab1 = QWidget()
self.Widget_choixPalette_Label = QLabel(self.tab1)
self.Widget_choixPalette_Label.setText("Text1")
self.Widget_choixPalette_ComboBox = QComboBox(self.tab1)
self.Widget_choixPalette_ComboBox.addItem("Try1")
self.Widget_choixPalette_ComboBox.addItem("Try2")
self.Bouton_quitter = QPushButton(self.tab1)
self.Bouton_quitter.setText("Quit")
def creation_layout(self):
LayoutForm = QGridLayout(self.Form)
LayoutG1 = QGridLayout()
LayoutTab1 = QGridLayout(self.tab1)
WidgetTemp = QWidget()
LayoutWidgetTemp = QGridLayout()
LayoutG1.addWidget(self.Bouton_quitter, 21, 29, 1, 2, Qt.AlignRight | Qt.AlignBottom)
LayoutG1.addWidget(self.canvas, 2, 10, 20, 20)
LayoutWidgetTemp.addWidget(self.Widget_choixPalette_Label, 0, 0, 1, 4)
LayoutWidgetTemp.addWidget(self.Widget_choixPalette_ComboBox, 1, 0, 1, 4)
WidgetTemp.setLayout(LayoutWidgetTemp)
LayoutG1.addWidget(WidgetTemp, 1, 18, 2, 4)
LayoutTab1.addLayout(LayoutG1, 0, 0, 1, 1)
self.tabWidget.addTab(self.tab1, " Tab1 ")
LayoutForm.addWidget(self.tabWidget, 1, 0, 1, 1)
def creation_figure(self):
# Create figure (transparent background)
self.figure = plt.figure()
self.figure.patch.set_facecolor('None')
self.canvas = FigureCanvas(self.figure)
self.canvas.setStyleSheet("background-color:transparent;")
# Adding one subplot for image
self.axe0 = self.figure.add_subplot(111)
self.axe0.get_xaxis().set_visible(False)
self.axe0.get_yaxis().set_visible(False)
plt.tight_layout()
# Data for init image
self.imageInit = [[255] * 320 for i in range(240)]
self.imageInit[0][0] = 0
# Init image and add colorbar
self.image = self.axe0.imshow(self.imageInit, interpolation='none')
divider = make_axes_locatable(self.axe0)
cax = divider.new_vertical(size="5%", pad=0.05, pack_start=True)
self.colorbar = self.figure.add_axes(cax)
self.figure.colorbar(self.image, cax=cax, orientation='horizontal')
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
# QApplication.setStyle(QStyleFactory.create("plastique"))
form = FenetrePrincipale()
form.show()
sys.exit(app.exec_())
Operating system: windows 7 Pro
Matplotlib version: 4.0.4
Matplotlib backend: Qt5Agg
Python version: 3.6
Other libraries: PyQt5
Edit 25/10/17 : new code for example
Below is a version of your example script that fixes all the issues. Most of the problems are caused by a very muddled use of layouts. I had to completely
re-write the creation_layout method in order to get a sane starting point so I could see where the problems were. I also temporarily restored the background colour of the canvas to make it easier to see how the widgets are layed out relative to each other. I realize that it won't be easy to incorporate some of my changes into your real code. But hopefully it will give you some ideas on how to simplify your layout structure.
The most important fix is the use of subplots_adjust in the creation_figure method. This removes all the empty space at the top of the canvas, so there is no longer any need to try to position other widgets on top of it.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
class FenetrePrincipale(QWidget):
def __init__(self, parent=None):
super(FenetrePrincipale, self).__init__(parent)
self.setupUi(self)
# Fonction de configuration de la classe
def setupUi(self, Form):
self.Form = Form
Form.setMinimumSize(1220, 850)
self.creation_GUI()
self.creation_figure()
self.creation_layout()
self.tabWidget.setCurrentIndex(0)
self.Bouton_quitter.clicked.connect(self.close)
def resizeEvent(self, QResizeEvent):
self.tabWidget.setMinimumSize(QSize(self.width() - 20, self.height() - 60))
def creation_GUI(self):
self.tabWidget = QTabWidget()
self.tab1 = QWidget()
self.tabWidget.addTab(self.tab1, " Tab1 ")
self.Widget_choixPalette_Label = QLabel(self.tab1)
self.Widget_choixPalette_Label.setText("Text1")
self.Widget_choixPalette_ComboBox = QComboBox(self.tab1)
self.Widget_choixPalette_ComboBox.addItem("Try1")
self.Widget_choixPalette_ComboBox.addItem("Try2")
self.Bouton_quitter = QPushButton(self.tab1)
self.Bouton_quitter.setText("Quit")
def creation_layout(self):
LayoutForm = QGridLayout(self)
LayoutForm.addWidget(self.tabWidget, 0, 0, 1, 1)
LayoutTab1 = QGridLayout(self.tab1)
LayoutTab1.addWidget(self.Widget_choixPalette_Label, 0, 1, 1, 1)
LayoutTab1.addWidget(self.Widget_choixPalette_ComboBox, 1, 1, 1, 1)
self.Widget_choixPalette_ComboBox.setMinimumWidth(200)
LayoutTab1.addWidget(self.canvas, 2, 0, 1, 3)
LayoutTab1.addWidget(self.Bouton_quitter, 2, 3, 1, 1, Qt.AlignRight | Qt.AlignBottom)
LayoutTab1.setRowStretch(2, 1)
LayoutTab1.setColumnStretch(0, 1)
LayoutTab1.setColumnStretch(2, 1)
def creation_figure(self):
# Create figure (transparent background)
self.figure = plt.figure()
# self.figure.patch.set_facecolor('None')
self.canvas = FigureCanvas(self.figure)
self.canvas.setStyleSheet("background-color:transparent;")
# Adding one subplot for image
self.axe0 = self.figure.add_subplot(111)
self.axe0.get_xaxis().set_visible(False)
self.axe0.get_yaxis().set_visible(False)
# plt.tight_layout()
# Data for init image
self.imageInit = [[255] * 320 for i in range(240)]
self.imageInit[0][0] = 0
# Init image and add colorbar
self.image = self.axe0.imshow(self.imageInit, interpolation='none')
divider = make_axes_locatable(self.axe0)
cax = divider.new_vertical(size="5%", pad=0.05, pack_start=True)
self.colorbar = self.figure.add_axes(cax)
self.figure.colorbar(self.image, cax=cax, orientation='horizontal')
plt.subplots_adjust(left=0, bottom=0.05, right=1, top=1, wspace=0, hspace=0)
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
# QApplication.setStyle(QStyleFactory.create("plastique"))
form = FenetrePrincipale()
form.show()
sys.exit(app.exec_())
Just reverse the order you add things to the layout. Add the canvas first, then the button on top
LayoutForm.addWidget(canvas,1,0,1,6)
LayoutForm.addWidget(button,1,0,1,2)

matplotlib - matplotlib not plot at the second time running in a thread

I have a small program that plots a figure with 4 subplots. I make a little GUI and call that plotting function from a thread, so each time I click the button, it should recollect data and redraw that figure (with 4 subplot), without closing the script. The script can plot for the first time, however at the second time (click button without closing the script), it stops at initialize the subplots. I have tried plt.close('all'), plt.close(fig), plt.clf(),... but it doesnt help. I really run out of idea why it stops at the second time.
Here is my full little script. Much appreciate for any inputs
import sys, os
import time
import wx
import traceback
from textwrap import wrap
import shutil
import itertools
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import threading
def test():
Do = Data()
Do.PlotGraph()
class Data(object):
def __init__(self):
self.SavePath = "C:\\Plots\\"
def f(self, t):
return np.exp(-t) * np.cos(2*np.pi*t)
def PlotGraph(self):
#Plotting
print "***** Generating plot"
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
print "Initialize subplots"
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows = 2, ncols = 2, frameon=False, figsize=(20, 10))
fontSize = 13
print "Creating ax1"
ax1title = "ax1"
ax1.set_title(ax1title, fontsize = fontSize)
ax1.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax2"
ax2title = "ax2"
ax2.set_title(ax2title, fontsize = fontSize)
ax2.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax3"
ax3title = "ax3"
ax3.set_title(ax3title, fontsize = fontSize)
ax3.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
print "Creating ax4"
ax4title = "ax4"
ax4.set_title(ax4title, fontsize = fontSize)
ax4.plot(t1, self.f(t1), 'bo', t2, self.f(t2), 'k')
fig.subplots_adjust(hspace = 0.35) #make room for axes title and x-axis label
fig.subplots_adjust(bottom = 0.07) #make room for axes title and x-axis label
fig.subplots_adjust(wspace = 0.30)
fig.subplots_adjust(top = .86)
filename= "Test"
if not os.path.exists(self.SavePath):
os.makedirs(self.SavePath)
savefilename = self.unique_file(self.SavePath, filename, "png")
print "***** Saving plot to: " + self.SavePath + savefilename
fig.savefig(self.SavePath + savefilename, dpi = 200)
plt.close(fig)
def unique_file(self, path, basename, ext):
actualname = "%s.%s" % (basename, ext)
c = itertools.count()
while os.path.exists(path + actualname):
actualname = "%s_[%d].%s" % (basename, next(c), ext)
#print "actualname: " + actualname
return actualname
################## THREAD UPDATE GUI ######################
#1. Create new custom event to update the display
DisplayEventType = wx.NewEventType();
EVT_DISPLAY = wx.PyEventBinder(DisplayEventType, 1);
def GetDataThreadStart(window):
GetDataThread(window)
class GetDataThread(threading.Thread):
def __init__(self, output_window):
threading.Thread.__init__(self)
self.output_window = output_window
print "Thread started"
self.start()
def run(self):
test()
print "Test Done\n\n"
self.UpdateFunction("Enable Go Button")
def UpdateFunction(self, msg):
evt = UpdateDisplayEvent(DisplayEventType, -1) #initialize update display event
evt.UpdateText(str(msg)); #update display event
wx.PostEvent(self.output_window, evt)
#Define event
class UpdateDisplayEvent(wx.PyCommandEvent):
def __init__(self, evtType, id):
wx.PyCommandEvent.__init__(self, evtType, id)
self.msg = ""
def UpdateText(self,text):
self.msg = text
def GetText(self):
return self.msg
######## Define GUI ###########
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
class MyFrame(wx.Frame):
def __init__(self, parent):
self.title = 'Testing'
wx.Frame.__init__(self, parent, -1, self.title, size = (300,350))
pnl = MyPanel(self)
self.Go_Button = wx.Button(pnl, -1, "Goooooo!")
self.Bind(wx.EVT_BUTTON, self.ClickGo, self.Go_Button)
BoxSizerMain = wx.BoxSizer(wx.VERTICAL)
BoxSizerMain.Add(self.Go_Button, 0, wx.ALIGN_CENTER_HORIZONTAL)
self.Bind(EVT_DISPLAY, self.OnThreadUpdate)
def ClickGo(self, event):
self.Go_Button.Disable()
GetDataThreadStart(self)
def OnThreadUpdate(self, event):
msg = event.GetText()
if msg == "Enable Go Button":
self.Go_Button.Enable()
def invokeGUI():
app = wx.PySimpleApp()
frame = MyFrame(None)
frame.Show()
frame.Iconize(True)
frame.Iconize(False)
frame.CenterOnScreen()
app.MainLoop()
if __name__ == '__main__':
invokeGUI()
The result
I believe you need to change the matplotlib backend to wx via the matplotlib.use() function in order to embed matplotlib plots within wx graphics.
Here's what I changed in your script:
import matplotlib
matplotlib.use('WX')
import matplotlib.pyplot as plt
This produced the following image after testing:
Console output:
Initialize subplots
Creating ax1
Creating ax2
Creating ax3
Creating ax4
***** Saving plot to: /Users/luccary/Downloads/foo/Test_[0].png
/Users/luccary/.virtualenvs/numpy/lib/python2.7/site-packages/matplotlib/cbook.py:136: MatplotlibDeprecationWarning: The WX backend is deprecated. It's untested and will be removed in Matplotlib 2.2. Use the WXAgg backend instead. See Matplotlib usage FAQ for more info on backends.
warnings.warn(message, mplDeprecation, stacklevel=1)
Test Done
Please note the deprecation warning, since wx will be removed as a backend in Matplotlib 2.2, and you should likely use wxagg instead.
References: See this matplotlib tutorial which I found after reading a Stack Overflow answer to a similar question here.
Hope that answers your question. Good luck!

Resources