how to embed Schemdraw inside PyQt - python

Schemdraw is using matplotlib to draw and show the schematic by python.
for example if you run the following code, it draws in matplot.
Now the question is how to embed this matplotlib inside PyQt5?
thanks
import schemdraw
import schemdraw.elements as elm
d = schemdraw.Drawing(fontsize=10)
d.add(elm.Capacitor())
r=d.add(elm.Resistor(theta=40))
d.add(elm.Diode(label="D1"))
d.draw()

With the version provided by pypi, you cannot embed schemdraw (at least in a simple way) but reviewing the repository I see that in the next release the draw method is modified to accept an axis so you must install schemdraw from the repository:
python -m pip install git+https://bitbucket.org/cdelker/schemdraw.git
Then modifying the official matplotlib example you can embed schemdraw:
import sys
from matplotlib.backends.qt_compat import QtCore, QtWidgets
if QtCore.qVersion() >= "5.":
from matplotlib.backends.backend_qt5agg import (
FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas,
NavigationToolbar2QT as NavigationToolbar,
)
from matplotlib.figure import Figure
import schemdraw
import schemdraw.elements as elm
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
canvas = FigureCanvas(Figure(figsize=(5, 3)))
layout.addWidget(canvas)
self.addToolBar(NavigationToolbar(canvas, self))
ax = canvas.figure.subplots()
d = schemdraw.Drawing(fontsize=10)
d.add(elm.Capacitor())
r = d.add(elm.Resistor(theta=40))
d.add(elm.Diode(label="D1"))
d.draw(ax=ax)
if __name__ == "__main__":
# Check whether there is already a running QApplication (e.g., if running
# from an IDE).
qapp = QtWidgets.QApplication.instance()
if not qapp:
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
app.activateWindow()
app.raise_()
qapp.exec_()

Related

PySide6 as a process in a script

I am running a data analysis script and I much prefer plotly to matplotlib. I am curious if it is possible to do the same figure windows like in matplotlib without blocking the script.
This post gave the following code that generates a WebEngineWindget (I used PySide6 here) with an offline plotly chart
import numpy as np
import plotly.graph_objs as go
import plotly.offline
fig = go.Figure()
fig.add_scatter(x=np.random.rand(100), y=np.random.rand(100), mode='markers',
marker={'size': 30, 'color': np.random.rand(100), 'opacity': 0.6,
'colorscale': 'Viridis'});
import os, sys
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QUrl
from PySide6 import QtWebEngineWidgets
class PlotlyViewer(QtWebEngineWidgets.QWebEngineView):
def __init__(self, fig, exec=True):
# Create a QApplication instance or use the existing one if it exists
self.app = QApplication.instance() if QApplication.instance() else QApplication(sys.argv)
super().__init__()
self.file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "temp.html"))
plotly.offline.plot(fig, filename=self.file_path, auto_open=False)
self.load(QUrl.fromLocalFile(self.file_path))
self.setWindowTitle("Plotly Viewer")
self.show()
if exec:
self.app.exec()
def closeEvent(self, event):
os.remove(self.file_path)
win = PlotlyViewer(fig)
My question is how I can do this without blocking my script. If I do
win = PlotlyViewer(fig)
win2 = PlotlyViewer(fig2)
win3 = PlotlyViewer(fig3)
Then win2 doesnt pop up until the first one is closed. I understand I could make a widget that has 3 instead, but sometimes I am just looking at 1 plot, other times more, so I am curious if I can run as a process that would keep executing. Or have I run into the thing I read everywhere where the GIL turns people away from python?

"event loop is already running" while using the pdb debugger in an application running with matplotlib and PySide2

I have an application in which the user has to click on images using matplotlib. I want to use the debugger pdb (line 53), however, when I launch the app and click on the button, a message
QCoreApplication::exec: The event loop is already running
appears and prevents me of using the debugger. I suspect that it comes from the following lines but I'm not sure
import matplotlib
try:
matplotlib.rcParams['backend.qt5'] = 'PySide2'
except (KeyError, Exception):
pass
matplotlib.use('Qt5Agg')
I did implement those lines thanks to the answer I got from this question I asked previously:
Same code with PyQT5/PySide2 runs on MacOS but throws an error on Linux
How could I retain the same code structure with backend and being able to use the pdb debugger?
import sys
import os
import subprocess
from subprocess import call
import matplotlib
import pdb
try:
matplotlib.rcParams['backend.qt5'] = 'PySide2'
except (KeyError, Exception):
pass
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2.QtWidgets import QWidget, QApplication, QPushButton, QFileDialog
from PySide2.QtCore import QFile, QTextStream
os.environ['QT_MAC_WANTS_LAYER'] = '1'
from IPython.core import ultratb
import pdb
sys.excepthook = ultratb.FormattedTB(mode='Verbose', color_scheme='Linux', call_pdb=True)
class GUI(QWidget):
def __init__(self):
super(GUI, self).__init__()
self.initUI()
def initUI(self):
height_btn = 40
width_btn = 350
button_position_x = 0
button_position_y = 20
button_position_x = button_position_x = 0
button_position_y = button_position_y + 400
btn15 = QPushButton('button', self)
btn15.clicked.connect(self.Plotfunction)
btn15.resize(width_btn, height_btn)
btn15.move(button_position_y, button_position_x)
self.show()
def Plotfunction(self):
pdb.set_trace()
print("ok")
def main():
app = QApplication(sys.argv)
ex = GUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
By default matplotlib will load the binding (PyQt or PySide2) that is already imported, or if there are none loaded then it will try to import them and the first one that manages to import it will use it. Apparently in the case of the OP it is first importing PyQt causing that problem. The solution is to import PySide2 and then matplotlib so that it prefers PySide2 as the backend binding.

plot doesn't update on button click but does when window resized

What's going on:
Program in python3/qt5/matplotlib. I am plotting three subplots and few plots in each. I wanted to update plots if user changes any data. That proved rather complicated hence the button 'Replot'. It calls a function that clears canvas and ... reloads the entire widget. It doesn't work as expected: when I press the button nothing happens, but when window gets resized after button clicked it does update the plot.
What would be a correct way to do it without having to put all the plots in that reload function?
I've managed to cut out a minimum code that does what I described - only it'd require the GUI file (created in qt5 designer), sensor log files and a settings file for configparser. (That indentation change on mplwidget is botched on purpose so that code displays as code. Didn't know how to make it display correctly otherwise)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys, os
from PyQt5.QtCore import Qt
from PyQt5 import QtCore, QtGui, QtWidgets, QtQuick
import matplotlib
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import dates
import numpy as np
import pandas as pd
import configparser
from RPGH_main_gui_signals_sorted import Ui_MainWindow
sys.path.append('..')
LOCAL_DIR = os.path.dirname(os.path.realpath(__file__)) + "/"
config_f = 'settings_config_parser.py'
cfg = configparser.ConfigParser()
cfg.read('settings_config_parser.py')
class DesignerMainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
super(DesignerMainWindow, self).__init__(parent)
self.setupUi(self)
self.replot_pushButton.clicked.connect(self.mpl_replot)
self.temperature_temp_max.setValue(cfg.getint('temperature', 'temp_max'))
self.temperature_temp_max.valueChanged.connect(self.write_temp_max)
self.mplwidget()
def mplwidget(self):
temp_max = cfg.getint('temperature', 'temp_max')
temp_log = 'sensor_logs/temperature_out.dat'
time_format = '%d,%m,%Y,%X'
temp_data = pd.read_csv(temp_log, header=None, delim_whitespace=True)
temp_data.columns = ['timestamp', 'temp_up', 'temp_down',
'temp_ground']
timestamp = pd.to_datetime(pd.Series(temp_data.timestamp),
format=time_format)
self.mpl.canvas.ax.plot(timestamp, temp_data.temp_up)
self.mpl.canvas.ax.fill_between(timestamp, temp_data.temp_up, temp_max,
where=temp_data.temp_up>=temp_max, edgecolor='red', facecolor='none',
hatch='/', interpolate=True)
def mpl_replot(self):
self.mpl.canvas.ax.clear()
self.mplwidget()
def write_temp_max(self):
send = self.temperature_temp_max.value()
message = str(send)
cfg.set('temperature', 'temp_max', message)
with open(config_f, 'w') as conffile:
cfg.write(conffile)
if __name__ == '__main__':
app = QtWidgets.QApplication([sys.argv])
gui = DesignerMainWindow()
gui.show()
sys.exit(app.exec_())

How to use matplotlib with PyQt4

I want to plot a figure with embedded matplotlib in PyQt.
I am using Qt Designer for the main window, and writing python code for the signal and slots connexion part.
So my code looks like this :
import sys
from PyQt4 import QtCore, QtGui, uic
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
qtCreatorFile = "main.ui" # my Qt Designer file
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.csvbutton.clicked.connect(self.plot)
def plot(self):
filePath="/path to csv file here"
df= pd.read_csv(str(filePath),index_col='date')
df.index = pd.to_datetime(df.index, unit='s')
ax = self.figure.add_subplot(111)
ax.hold(False)
ax.plot(df, '*-')
self.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
My main problem is the connexion between the Qt Designer file and the python code, I couldn't set a canvas widget directly in Qt Designer and I'm still struggling to find where the error lays in my code.
Your help is very appreciated, thank you.
In order to use matplotlib in Qt Designer can not be done directly, for this we must promote a QWidget to use FigureCanvas or better a class that inherits from it as I show below, first we create a class called Canvas in file called canvas.py:
canvas.py
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
class Canvas(FigureCanvas):
def __init__(self, parent=None):
self.figure = plt.figure()
FigureCanvas.__init__(self, self.figure)
self.setParent(parent)
After creating the design through Qt Designer, we have all the elements we want, but where we want to place the argument we use the Widget element that is in Containers, and we name it canvas:
Then we promote it by right click and choose the option promoted to ...:
Obtaining what is shown in the following image, in Promoted Class Name we place Canvas as the name of the class, and in Header File we place canvas.h (in Header File the file.py file is placed, for example package.subpackage.file.h), then press Add and after Promote:
At the end we get a file structure similar to the following:
.
├── canvas.py
└── main.ui
Then we create the file main.py where we place your code with small variations:
main.py
import matplotlib
matplotlib.use('Qt4Agg')
import sys
from PyQt4 import QtCore, QtGui, uic
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
qtCreatorFile = "main.ui" # my Qt Designer file
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.csvbutton.clicked.connect(self.plot)
def plot(self):
filePath="data.csv"
df= pd.read_csv(str(filePath),index_col='date')
ax = self.canvas.figure.add_subplot(111)
ax.hold(False)
ax.plot(df, '*-')
self.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
In the end we get the following:
You can find the complete project here
If you want to add the NavigationToolbar you can use the following code:
...
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
...
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.addToolBar(NavigationToolbar(self.canvas, self))
...

Python script crashes when closing after running matplotlib with qt4agg because of import

I have written a script using matplotlib, which runs just fine with the standard matplotlib. The script is written with the plot as a class, and calling Plot() is enough to get it running.
Now I want to add some buttons to the toolbar, and to do this I am using qt4agg because I have installed matplotlib via Anaconda. When writing the code for the main window I used this example, and it runs just fine. In order to use the plot script I have already written I want to pass the figure created in the QT-script to the Plot()-class.
This solution works just fine, until I try to close the window. The window closes, and python crashes. It crashes even though I do not call the Plot()-class, and the only way to get it to not crash is to remove the line importing the file. Is there something special I need to think about when importing a script into a window?
from __future__ import print_function
import sys
from matplotlib.figure import Figure
from matplotlib.backend_bases import key_press_handler
### LINE CAUSING TROUBLE
from plotting import Plot
###
from test import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends import qt_compat
use_pyside = qt_compat.QT_API == qt_compat.QT_API_PYSIDE
if use_pyside:
print ("USING PYSIDE")
from PySide.QtCore import *
from PySide.QtGui import *
else:
print("NOT USING PYSIDE")
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class AppForm(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.create_main_frame()
self.on_draw()
def create_main_frame(self):
self.main_frame = QWidget()
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.setFocusPolicy(Qt.StrongFocus)
self.canvas.setFocus()
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.canvas.mpl_connect('key_press_event', self.on_key_press)
vbox = QVBoxLayout()
vbox.addWidget(self.canvas)
vbox.addWidget(self.mpl_toolbar)
self.main_frame.setLayout(vbox)
self.setCentralWidget(self.main_frame)
def on_draw(self):
self.fig.clear()
#Plot(self.fig)
self.canvas.draw()
def on_key_press(self, event):
key_press_handler(event, self.canvas, self.mpl_toolbar)
def main():
app = QApplication(sys.argv)
form = AppForm()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Here is a very compressed version of the other file that still causes the error.
import matplotlib.pyplot as pplt
class Plot():
def __init__(self, figure=pplt.figure()):
self.figure = figure
i had the same problems with Anaconda and Python(x,y). then i tried to install Python from scratch :
python-2.7.10.msi
PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe
VCForPython27.msi
pip install matplotlib
it doesn't solve all crashes. you could also try this :
self.setAttribute(Qt.WA_DeleteOnClose)
for example :
class AppForm(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setAttribute(Qt.WA_DeleteOnClose) # <---
self.create_main_frame()
self.on_draw()
...

Categories