PyQt5 to make subplots (upto 10 graphs) in the same widget - python-3.x

I have developed an UI to read large CSV file and plot. The problem is I can only plot one graph at a time. I need to select upto 10 variable and plot it in the same widget.
My UI should look like this
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
from PyQt5 import QtCore, QtGui, QtWidgets
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QFileDialog
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as Navi
from matplotlib.figure import Figure
import seaborn as sns
import pandas as pd
import sip # can be installed : pip install sip
from datetime import datetime
from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5 import *
import sys
# We require a canvas class
from mpldatacursor import datacursor
class MatplotlibCanvas(FigureCanvasQTAgg):
def __init__(self,parent=None, dpi = 120):
fig = Figure(dpi = dpi)
self.axes = fig.add_subplot(111)
super(MatplotlibCanvas,self).__init__(fig)
fig.tight_layout()
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1440, 1000)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setObjectName("label")
self.horizontalLayout.addWidget(self.label)
self.label_1 = QtWidgets.QLabel(self.centralwidget)
self.label_1.setObjectName("label_1")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setObjectName("label_3")
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.comboBox.setObjectName("comboBox")
self.horizontalLayout.addWidget(self.comboBox)
self.comboBox_1 = QtWidgets.QComboBox(self.centralwidget)
self.comboBox_1.setObjectName("comboBox_1")
self.comboBox_2 = QtWidgets.QComboBox(self.centralwidget)
self.ComboBox_2 = CheckableComboBox()
self.comboBox_2.setObjectName("comboBox_2")
self.comboBox_2.setEditable(True)
#self.comboBox_2.setFocusPolicy( Qt.StrongFocus )
#self.comboBox_2.lineEdit().setMaxLength(45)
#self.comboBox_2.setReadOnly(True)
#self.comboBox_2.textChanged.connect(self.doSomething)
self.radioButton = QtWidgets.QRadioButton(self.centralwidget)
self.radioButton.setObjectName("radioButton")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout.addWidget(self.pushButton)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.horizontalLayout.addWidget(self.label_1)
self.horizontalLayout.addWidget(self.comboBox_1)
self.horizontalLayout.addWidget(self.label_2)
self.horizontalLayout.addWidget(self.comboBox_2)
self.horizontalLayout.addWidget(self.label_3)
self.horizontalLayout.addWidget(self.radioButton)
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.verticalLayout.addItem(self.spacerItem1)
self.gridLayout.addLayout(self.verticalLayout, 1, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.actionOpen_csv_file = QtWidgets.QAction(MainWindow)
self.actionOpen_csv_file.setObjectName("actionOpen_csv_file")
self.actionExit = QtWidgets.QAction(MainWindow)
self.actionExit.setObjectName("actionExit")
self.menuFile.addAction(self.actionOpen_csv_file)
self.menuFile.addAction(self.actionExit)
self.menubar.addAction(self.menuFile.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.filename = ''
self.canv = MatplotlibCanvas(self)
self.df = []
self.toolbar = Navi(self.canv,self.centralwidget)
self.horizontalLayout.addWidget(self.toolbar)
self.themes = ['bmh', 'classic', 'dark_background', 'fast',
'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn-bright',
'seaborn-colorblind', 'seaborn-dark-palette', 'seaborn-dark',
'seaborn-darkgrid', 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook',
'seaborn-paper', 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk',
'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', 'seaborn',
'Solarize_Light2', 'tableau-colorblind10']
self.comboBox.addItems(self.themes)
self.comboBox_1.addItems(['Select horizontal axis here'])
self.comboBox_2.addItems(['Select vertical axis here'])
self.pushButton.clicked.connect(self.getFile)
self.comboBox.currentIndexChanged['QString'].connect(self.Update)
self.comboBox_1.currentIndexChanged['QString'].connect(self.selectXaxis)
self.comboBox_2.currentIndexChanged['QString'].connect(self.selectYaxis)
self.actionExit.triggered.connect(MainWindow.close)
self.actionOpen_csv_file.triggered.connect(self.getFile)
self.radioButton.clicked.connect(self.vsAll)
self.dataset={}
self.x_axis_slt=None
self.y_axis_slt=None
self.vsall = False
def vsAll(self):
"""
This function will be called upon triggering the radio check button. If set to True, all the columsn in the csv
will be plotted against the x-axis column. Please note that vs all means versus all, so that whatever value is
selected as the x-axis, it wont be plotted against itself in this mode. Moreover, the time series data will be
dedicated for the datetime x-axis and it wont be displayed in the vs all contents.
"""
if self.vsall==False:
self.vsall=True
else:
self.vsall=False
self.Update(self.themes[0])
def selectXaxis(self,value):
"""
This function will update the plot according to the data of x axis selected from combo box
"""
self.x_axis_slt=value
self.Update(self.themes[0])
def selectYaxis(self,value):
"""
This function will update the plot according to the data of y axis selected from combo box
"""
self.y_axis_slt=value
self.Update(self.themes[0])
def Update(self,value):
"""
This function will input the value of theme and accordingly plot the data, if the data is relative, i.e., x verus y-axis
then the user can assign x and y axis from the combo box. If all data should be plotted in paraller then leave,
the combo boxes of axis selections to their default starting location.
"""
plt.clf()
plt.style.use(value)
try:
self.horizontalLayout.removeWidget(self.toolbar)
self.verticalLayout.removeWidget(self.canv)
sip.delete(self.toolbar)
sip.delete(self.canv)
self.toolbar = None
self.canv = None
self.verticalLayout.removeItem(self.spacerItem1)
except Exception as e:
print(e)
pass
self.canv = MatplotlibCanvas(self)
self.toolbar = Navi(self.canv,self.centralwidget)
self.horizontalLayout.addWidget(self.toolbar)
self.verticalLayout.addWidget(self.canv)
self.canv.axes.cla()
ax = self.canv.axes
try:
if self.vsall:
for k,v in self.dataset.items():
if k!=self.x_axis_slt and type(v[0])!=datetime:
lines = ax.plot(self.dataset[self.x_axis_slt],v,label=k)
datacursor(lines)
legend = ax.legend()
legend.set_draggable(True)
ax.set_xlabel(self.x_axis_slt)
ax.set_ylabel('ALL OTHERS')
ax.set_title(self.Title)
#plt.setp(ax.xaxis.get_majorticklabels(), rotation=25) # uncomment if you want the x-axis to tilt 25 degree
else:
lines = ax.plot(self.dataset[self.x_axis_slt],self.dataset[self.y_axis_slt],label=self.y_axis_slt)
datacursor(lines)
legend = ax.legend()
legend.set_draggable(True)
ax.set_xlabel(self.x_axis_slt)
ax.set_ylabel(self.y_axis_slt)
ax.set_title(self.Title)
#plt.setp(ax.xaxis.get_majorticklabels(), rotation=25) # uncomment if you want the x-axis to tilt 25 degree
except Exception as e:
print(e)
try:
lines = self.df.plot(ax = self.canv.axes)
datacursor(lines)
legend = ax.legend()
legend.set_draggable(True)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_title(self.Title)
except Exception as e:
print(e)
pass
pass
self.canv.draw()
def getFile(self):
""" This function will get the address of the csv file location
also calls a readData function
"""
try:
self.filename = QFileDialog.getOpenFileName(filter = "csv (*.csv)")[0]
self.readData()
except Exception as e:
print(e)
pass
def getDataset(self,csvfilename):
"""
This function will convert csv file to a dictionary of dataset, with keys as the columns' names and
values as values. The datatime format should be one of the standard datatime formats. Before plottting
we need to convert the string of data time in the csv file values to datatime format.
"""
df = pd.read_csv(csvfilename,sep=';')
LIST_OF_X_COLUMNS = ['Time[s]']
LIST_OF_Y_COLUMNS = df.columns
dataset={}
# time_format = "%d/%m/%Y %H:%M:%S"
# time_format = "%m/%d/%Y"
# time_format = "%d/%m/%Y"
# time_format = "%m-%d-%Y"
# time_format = "%d-%m-%Y"
# time_format = "%H:%M:%S"
# time_format = "%M:%S"
# time_format = '%d/%m/%Y %H:%M%f'
#time_format = '%Y-%m-%d %H:%M:%S.%f'
for col in LIST_OF_Y_COLUMNS:
dataset[col] = df[col].iloc[0:].values
try:
dataset[col] = [datetime.strptime(i) for i in df[col].iloc[0:].values]
except Exception as e:
pass
print(e)
return dataset, LIST_OF_X_COLUMNS, LIST_OF_Y_COLUMNS
def readData(self):
""" This function will read the data using pandas and call the update
function to plot
"""
import os
self.dataset={}
self.x_axis_slt=None
self.y_axis_slt=None
base_name = os.path.basename(self.filename)
self.Title = os.path.splitext(base_name)[0]
self.dataset, LIST_OF_X_COLUMNS, LIST_OF_Y_COLUMNS = self.getDataset(self.filename)
chunksize = 10 ** 6
self.df = pd.read_csv(self.filename,sep=';',chunksize = chunksize)
self.Update(self.themes[0]) # lets 0th theme be the default : bmh
self.comboBox_1.clear()
#self.comboBox_2.clear()
self.comboBox_2.addItems(['Select vertical axis here'])
self.comboBox_1.addItems(LIST_OF_X_COLUMNS)
self.comboBox_2.addItems(LIST_OF_Y_COLUMNS)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "Select Theme"))
self.label_1.setText(_translate("MainWindow", "X-axis"))
self.label_2.setText(_translate("MainWindow", "Y-axis"))
self.label_3.setText(_translate("MainWindow", "vs all"))
self.pushButton.setText(_translate("MainWindow", "Open"))
self.menuFile.setTitle(_translate("MainWindow", "File"))
self.actionOpen_csv_file.setText(_translate("MainWindow", "Open csv file"))
self.actionExit.setText(_translate("MainWindow", "Exit"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
This is what I have and the output looks like. But, I need to get the output as shown in picture 1.
I'm very new to PyQt5. So, could anyone please help me with this. Thanks in advance.
Output from UI I have develop

Related

Is there a way to use a live plot within a pyqt5 groupbox using python?

I am using qt designer to create a human machine interface. Part of my project is to have a live distance vs force plot. I have successfully created the plot using matplot and pandas libraries, and I'm using a python script to simulate sensor values. I stripped my code down to basic components to help me conceptualize and to make it easier to provide help. Aside from being confused about how python classes, objects, and the reference to "self" works at times, I cannot seem to attach a live plot to a pyqt5 plot groupbox.
Below is my data generation code. Note that if you run it. A csv. file will be created within the file directory.
import csv
import random
import time
import sys #Allows system Arguments
def datagen():
sec=1
distance = 30
force = 0
Flag = True
fieldnames = ["seconds", "distance", "force"]
with open('distanceVsforce_data.csv', 'w') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
csv_writer.writeheader()
while Flag:
#Saved to home directory, cannot write and read from excel at the same time
with open('distanceVsforce_data.csv', 'a') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
info = {
"distance": distance,
"force": force
}
csv_writer.writerow(info)
distance += 1
force = force + random.randint(1, 10)
if force > 102:
Flag = False
force = 100
print(distance, force)
time.sleep(sec) #(1 seconds)
while force >= 0:
#Saved to home directory, cannot write and read from excel at the same time
with open('distanceVsforce_data.csv', 'a') as csv_file:
csv_writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
info = {
"distance": distance,
"force": force
}
csv_writer.writerow(info)
distance += 1
force -= 10
print(distance, force)
time.sleep(sec)
if __name__=="__main__":
datagen()
Directly after running the script from above, I run the python script below. If they are run one after the other, a live updating plot will be displayed.
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def plot():
plt.style.use('ggplot')
def animate(i):
data = pd.read_csv('distanceVsforce_data.csv')
x = data['distance']
y = data['force']
plt.cla()
plt.plot(x,y, label='Distance vs Force', marker='.', color='#da291c')
ani = FuncAnimation(plt.gcf(), animate, interval=100) #(1 second = 1000)
plt.show()
if __name__ == "__main__":
plot()
Resulting Plot:
After stripping my interface designed with qt designer window down to a plot groupbox and a widget, I thought it would be easier to attach my plot code to the window. However, I was still unsuccessful. The code that I tossed together, containing the pyqt5 window with a plot group box and my plot code, can be found below. Note that the code produces the plot first as figure 1 and once the window closes an empty pyqt5 window with a plot groupbox appears next.
from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(996, 716)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setGeometry(QtCore.QRect(30, 20, 941, 611))
self.groupBox.setObjectName("groupBox")
self.widget = QtWidgets.QWidget(self.groupBox)
self.widget.setGeometry(QtCore.QRect(29, 39, 881, 541))
self.widget.setObjectName("widget")
def animate(i):
data = pd.read_csv('distanceVsforce_data.csv')
x = data['distance']
y = data['force']
plt.plot(x,y)
ani = FuncAnimation(plt.gcf(), animate, interval=10) #(1 second = 1000)
plt.show()
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.groupBox.setTitle(_translate("MainWindow", "Plot"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

Why Matplotlib.rcParams.update style do not update facecolor PyQt5?

I am using matplotlib styles and I try to change the styles dynamically when the style is chosen in the combobox. I am using Matplotlib 2.2.3, Python 3.6.6, PyQt5, Windows 10. But when I choose the dark_background style, the figure facecolor and the axes facecolor do not change. Here an animation:
This is the code:
File IHMDrawDates.py generated with pyuic5:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MplMainWindow(object):
def setupUi(self, MplMainWindow):
MplMainWindow.setObjectName("MplMainWindow")
MplMainWindow.resize(628, 416)
self.centralwidget = QtWidgets.QWidget(MplMainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout_2.setObjectName("gridLayout_2")
self.mpl = MplWidgetTest(self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.mpl.sizePolicy().hasHeightForWidth())
self.mpl.setSizePolicy(sizePolicy)
self.mpl.setObjectName("mpl")
self.gridLayout_2.addWidget(self.mpl, 0, 0, 1, 1)
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setMaximumSize(QtCore.QSize(95, 16777215))
self.groupBox.setObjectName("groupBox")
self.gridLayout = QtWidgets.QGridLayout(self.groupBox)
self.gridLayout.setObjectName("gridLayout")
self.buttonDrawDate = QtWidgets.QPushButton(self.groupBox)
self.buttonDrawDate.setMaximumSize(QtCore.QSize(75, 16777215))
self.buttonDrawDate.setObjectName("buttonDrawDate")
self.gridLayout.addWidget(self.buttonDrawDate, 1, 0, 1, 1)
self.buttonErase = QtWidgets.QPushButton(self.groupBox)
self.buttonErase.setMaximumSize(QtCore.QSize(75, 16777215))
self.buttonErase.setObjectName("buttonErase")
self.gridLayout.addWidget(self.buttonErase, 2, 0, 1, 1)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 3, 0, 1, 1)
self.comboTema = QtWidgets.QComboBox(self.groupBox)
self.comboTema.setObjectName("comboTema")
self.gridLayout.addWidget(self.comboTema, 0, 0, 1, 1)
self.gridLayout_2.addWidget(self.groupBox, 0, 1, 1, 1)
MplMainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MplMainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 628, 21))
self.menubar.setObjectName("menubar")
MplMainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MplMainWindow)
self.statusbar.setObjectName("statusbar")
MplMainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MplMainWindow)
QtCore.QMetaObject.connectSlotsByName(MplMainWindow)
def retranslateUi(self, MplMainWindow):
_translate = QtCore.QCoreApplication.translate
MplMainWindow.setWindowTitle(_translate("MplMainWindow", "MainWindow"))
self.groupBox.setTitle(_translate("MplMainWindow", "GroupBox"))
self.buttonDrawDate.setText(_translate("MplMainWindow", "Draw"))
self.buttonErase.setText(_translate("MplMainWindow", "Erase"))
from mplwidgettest import MplWidgetTest
mplwidgettest.py file that contains the method 'setTema' to update matplotlib's style. When I print the variable rcParams it contains axes.facecolor: black but it does not apply
from PyQt5.QtWidgets import QSizePolicy, QWidget, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
import matplotlib as mplib
class MplCanvas(FigureCanvas):
"""Class to represent the FigureCanvas widget"""
def __init__(self):
# setup Matplotlib Figure and Axis
mplib.rcParams.update(mplib.rcParamsDefault)
mplib.style.use('bmh')
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
# initialization of the canvas
FigureCanvas.__init__(self, self.fig)
# we define the widget as expandable
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
# notify the system of updated policy
FigureCanvas.updateGeometry(self)
def setTema(self, tema='classic'):
print(tema)
mplib.style.use(tema)
print(mplib.rcParams)
mplib.rcParams.update(mplib.rcParams)
class MplWidgetTest(QWidget):
"""Widget defined in Qt Designer"""
def __init__(self, parent=None):
# initialization of Qt MainWindow widget
QWidget.__init__(self, parent)
# set the canvas to the Matplotlib widget
self.canvas = MplCanvas()
# create a NavigatioToolbar
self.ntb = NavigationToolbar(self.canvas, self)
# create a vertical box layout
self.vbl = QVBoxLayout()
# add mpl widget to vertical box
self.vbl.addWidget(self.canvas)
# add NavigationToolBar to vertical box
self.vbl.addWidget(self.ntb)
# set the layout to th vertical box
self.setLayout(self.vbl)
mainMplWidget.py File that calls the two previous files, and contains the method 'cambiarTema' that changes the matplotlib's style
import sys
from IHMDrawDates import Ui_MplMainWindow
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtGui import QCursor
import numpy as np
class DesignerMainWindow(QMainWindow, Ui_MplMainWindow):
def __init__(self, parent=None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
self.posX = []
self.posY = []
temas = ['bmh', 'classic', 'grayscale', 'seaborn-bright',
'ggplot', 'dark_background']
self.comboTema.addItems(temas)
# connect the signals with the slots
self.buttonDrawDate.clicked.connect(self.drawDate)
self.buttonErase.clicked.connect(self.eraseDate)
self.comboTema.currentIndexChanged.connect(self.cambiarTema)
def cambiarTema(self):
tema = self.comboTema.currentText()
self.mpl.canvas.setTema(str(tema))
self.mpl.canvas.ax.clear()
self.mpl.canvas.draw()
self.mpl.canvas.flush_events()
def drawDate(self):
x = np.arange(0, 100, 0.1)
y = np.sin(x)
self.mpl.canvas.ax.plot(x, y)
self.mpl.canvas.ax.relim()
self.mpl.canvas.ax.autoscale(True)
self.mpl.ntb.update()
self.mpl.ntb.push_current()
self.mpl.canvas.draw()
def eraseDate(self):
self.mpl.canvas.ax.clear()
self.mpl.ntb.update()
self.mpl.ntb.push_current()
self.mpl.canvas.draw()
if __name__ == '__main__':
app = 0
app = QApplication(sys.argv)
dmw = DesignerMainWindow()
# show it
dmw.show()
sys.exit(app.exec_())
The following is an explanation why setting a new style will not update the existing figure.
The matplotlib.rcParams is essentially a dictionary which stores certain default parameters that are meant to be used when creating objects in matplotlib.
rcParams.update will update this dictionary; update is a method of the python dict object. matplotlib.style.use is a shortcut to updating the rcParams, it will load the respective parameters from a file or dictionary.
Those parameters are then used when a new object is created. In a simplified fashion this would look like
def create_object(arg1, parameter1=None):
if not parameter1:
# use the default from the rcParams
parameter1 = rcParams["parameter1"]
obj = MatplotlibObject(arg1, parameter1=parameter1)
return obj
Such function is used when creating objects. However, once such object is created, it will not be run again when something in rcParams changes.
obj = create_object(1)
rcParams.update({"parameter1" : "New Value"})
# at this point, obj would not know about the new parameter1 value
As commented already, there are two options.
Change the parameter manually. You may change the parameter manually after the object has been created.
obj = create_object(1)
obj.set_parameter1("New Value")
Recreate the object. You may remove the existing object, update the rcParams and recreate the object such that it'll use the new value.
obj = create_object(1)
del obj
rcParams.update({"parameter1" : "New Value"})
obj = create_object(1)
The only solution I have found so far is to add these two lines in the 'setTema' method of the file mplwidgettest.py
self.fig.set_facecolor(mplib.rcParams['figure.facecolor'])
self.ax.set_facecolor(mplib.rcParams['axes.facecolor'])
The complete class is:
class MplCanvas(FigureCanvas):
"""Class to represent the FigureCanvas widget"""
def __init__(self):
# setup Matplotlib Figure and Axis
mplib.rcParams.update(mplib.rcParamsDefault)
mplib.style.use('bmh')
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
# initialization of the canvas
FigureCanvas.__init__(self, self.fig)
# we define the widget as expandable
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
# notify the system of updated policy
FigureCanvas.updateGeometry(self)
def setTema(self, tema='classic'):
mplib.rcParams.update(mplib.rcParamsDefault)
mplib.style.use(tema)
self.fig.set_facecolor(mplib.rcParams['figure.facecolor'])
self.ax.set_facecolor(mplib.rcParams['axes.facecolor'])
An animation of the result:
However, I do not think that each of the attributes of the figure has to be changed, if they are supposed to be defined in the style. Blessed matplotlib :-(

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)

How to get x y coordinates from a Matplotlib scatterplot graphic

I have a Widget into my GUI hosting a scatterplot graphic. I managed to include a pick event but am not able to get the x y coordinate by clicking a plot only the print('yahoo') works. My code is based on the following article: see link (section: Simple picking example).
How can I get the coordinates of the point (e.g. x=20 and y=50 which is one my five points) by clicking the point on the scatterplot?
Front-end code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(595, 393)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setMaximumSize(QtCore.QSize(100, 16777215))
self.pushButton.setObjectName("pushButton")
self.gridLayout.addWidget(self.pushButton, 0, 0, 1, 1)
self.widget = QtWidgets.QWidget(self.centralwidget)
self.widget.setMinimumSize(QtCore.QSize(0, 200))
self.widget.setStyleSheet("background-color: rgb(255, 255, 255);")
self.widget.setObjectName("widget")
self.gridLayout.addWidget(self.widget, 0, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 595, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Back-end code:
import sys
import matplotlib.pyplot as plt
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget, QSizePolicy
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from front_end import Ui_MainWindow
class Graph_init(FigureCanvas):
def __init__(self, parent=None):
fig = Figure()
fig.patch.set_facecolor("None")
self.axes = fig.add_subplot(111)
self.compute_initial_figure()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
FigureCanvas.mpl_connect(self, 'pick_event', self.onclick)
def onclick(self, event):
print('yahoo')
thisline = self.event.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
class Graph_populate(Graph_init):
def compute_initial_figure(self):
x = [10,20,30,40,50]
y = [100,50,150,200,75]
size = [1000,2000,3000,5000,2000]
self.axes.scatter(x,y,s=size,color='blue', picker=1)
self.axes.patch.set_facecolor('None')
class GUI(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(GUI, self).__init__(parent)
self.setupUi(self)
self.sc = Graph_populate(self.widget)
self.gridLayout.addWidget(self.sc, 0, 1, 1, 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
prog = GUI()
prog.showMaximized()
sys.exit(app.exec_())
Thank you
You are plotting a scatterplot. Hence you may have a look at the third part of the pick_event_demo, where a scatterplot is treated.
The idea would be to get the index of the picked datapoint and take the data at that index out to obtain the coordinates.
def onclick(self, event):
ind = event.ind[0]
data = event.artist.get_offsets()
xdata, ydata = data[ind,:]
print ((xdata, ydata))
Note that those are the coordintes of the data point, not those of the mouseclick (which is how I understand the question).

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