I am working on a GUI application, which generates graphs using Matplotlib package, for gui design i am using PyQt5.
In this application users loads the data from a line and then on pressing the generate button, a processed graph is generated, now the problem is that, on closing the graph, when user loads the new data, and press the generate button, graph is not displayed again.
Code
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton
import numpy as np
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
plt.subplots_adjust(hspace=0)
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 100, 100)
self.setWindowTitle('Generate Graph')
self.home()
def home(self):
btn = QPushButton('Generate', self)
btn.clicked.connect(self.generate_graph)
#btn.resize(100, 100)
#btn.move(100, 100)
self.show()
def generate_graph(self):
# In real application these points gets updated
x = [0,1,2,3,4,5,6,7,8,9]
y1 = [0,1,2,3,4,5,6,7,8,9]
y2 = [0,1,2,3,4,5,6,7,8,9]
ax1.plot(x,y1)
ax2.plot(x,y2)
plt.show()
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
So i am posting the sample program which can show my problem, in this i created a button and generated two plots.
(Note: these are two subplots, i created two subplots because, i need to write ylabel on the adjacent axis, so it is a requirement i can't change and it must be like this)
I pressed the generate button, graph gets generated.
I closed the graph, and again pressed the generate button but its not re-generated.
Please suggest me what i can add to make this happen.
Is it possible to generate new graph every-time user presses the generate button, i think this will also solve the problem.
Please suggest and thanks in advance.
I had searched with this topic on this forum, and tried various thing like clearing the axis etc etc, but i think i am doing something wrong as i am new to all this.
You're mixing matplotlib.pyplot's show GUI with another PyQt GUI. The problem is that the figure to show in the matplotlib GUI is created only once. As soon as it's closed, it's lost.
The simple solution is to create it within the generate_graph function. Thereby a new figure is created and shown every time the button is pressed.
import sys
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
import matplotlib.pyplot as plt
class window(QMainWindow):
def __init__(self):
super(window, self).__init__()
self.setGeometry(50, 50, 100, 100)
self.setWindowTitle('Generate Graph')
self.home()
def home(self):
btn = QPushButton('Generate', self)
btn.clicked.connect(self.generate_graph)
self.show()
def generate_graph(self):
fig, (ax1, ax2) = plt.subplots(nrows=2, sharex=True)
plt.subplots_adjust(hspace=0)
x = [0,1,2,3,4,5,6,7,8,9]
y1 = [0,1,2,3,4,5,6,7,8,9]
y2 = [0,1,2,3,4,5,6,7,8,9]
ax1.plot(x,y1)
ax2.plot(x,y2)
plt.show()
def run():
app = QApplication(sys.argv)
Gui = window()
sys.exit(app.exec_())
run()
Related
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?
I am trying to muddle my way through embedding a matplotlib figure inside of a Qt GUI created using Qt Designer. I am already able to create the figure I want just through Python. I've created a basic GUI with widgets to allow me to select/load input files, and plot the data in those files in a matplotlib figure that is embedded in the GUI. I accomplish this by adding a blank widget to the GUI called plotwidget, and then calling the GraphInit class with this widget as input.
The problem I am currently facing is that while my plot shows up and updates as desired inside the GUI, it doesn't seem to pay attention to the size policy defined for it in either my Python code (where the FigureCanvas is created) or for the plotWidget widget in Qt Designer. I have been using this demo, among others, as a starting point for this project. However, all of these examples have generated the GUI entirely from within Python. I suspect I am doing something wrong in assigning the matplotlib figure to the widget, but I haven't been able to figure out what.
Code (import & plotting code removed, but the core where figure is added is there):
import sys
import os
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMessageBox, QFileDialog, QPushButton, QLabel, QRadioButton, QDoubleSpinBox, QSpinBox, QWidget, QSizePolicy, QMainWindow
import PyQt5.uic
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import json
from datetime import datetime
import numpy as np
class LogViewer(QApplication):
def __init__(self):
QApplication.__init__(self, sys.argv)
self.ui = UI()
#staticmethod
def main():
vwr = LogViewer()
vwr.run()
def run(self):
self.ui.win.show() # Show the UI
self.ui.run() # Execute the UI run script
self.exec_()
class Graph_init(FigureCanvas):
def __init__(self, parent=None):
fig = Figure()
self.axes = fig.add_subplot(111)
self.compute_initial_figure()
self.axes.grid()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class UI(object):
def __init__(self):
ui_path = os.path.dirname(os.path.realpath(__file__))
self.win = PyQt5.uic.loadUi(ui_path + '\\logview.ui')
self.filename = ui_path + '\\test.txt'
self.plotWin = Graph_init(self.win.plotWidget)
def run(self):
self.init_ui() # get initial values from the controllers
self.attach_ui_connections()
def init_ui(self):
w = self.win
w.txtLogFilename.setText(self.filename.split('/')[-1]) # just the file (no path)
def attach_ui_connections(self):
w = self.win
if __name__ == '__main__':
LogViewer.main()
GUI is available here. Any suggestions appreciated!
I checked your ui file and plotWidget has no layout at all. Try to give it one, I suggest a grid layout.
Then, after parenting the canvas
self.setParent(parent)
try adding it to the parent layout:
parent.layout().addWidget(self)
I'm trying to embed multiple matplotlib plots in a multi-column layout in a PyQt GUI. At first sight I succeed in setting up the layout as wanted but when moving the mouse over any of the canvasses they change size and 'flicker'. When pressing the zoom button on the toolbar this becomes more pronounced.
For each matplotlib canvas I have connected a toolbar. If I do not connect the toolbars the problem does not appear. I have tried arranging the toolbars and canvases several ways - with a QGridLayout or nested QVBoxLayouts and QHBoxLayouts. Either way the problem appears if there are plots along side each other. If I put all plots in a single column it does not.
I have tried this in Python 3.6 in Windows (Anaconda 5.0.1) with PyQt4 and Python 3.5.2 in Linux (KDE Neon 64 bit) and with both PyQt4 and PyQt5 (v. 5.7.1), matplotlib 1.5.1 but with the same result. I have also tried using add_axes instead of add_subplot. Can someone help me understand what is causing this or find some kind of workaround? I can not use matplotlib subplots.
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
figure1 = Figure()
figure2 = Figure()
figure3 = Figure()
figure4 = Figure()
canvas1 = FigureCanvas(figure1)
canvas2 = FigureCanvas(figure2)
canvas3 = FigureCanvas(figure3)
canvas4 = FigureCanvas(figure4)
ax1 = figure1.add_subplot(111)
ax2 = figure2.add_subplot(111)
ax3 = figure3.add_subplot(111)
ax4 = figure4.add_subplot(111)
toolbar1 = NavigationToolbar(canvas1, self)
toolbar2 = NavigationToolbar(canvas2, self)
toolbar3 = NavigationToolbar(canvas3, self)
toolbar4 = NavigationToolbar(canvas4, self)
mainLayout = QtWidgets.QGridLayout()
mainLayout.addWidget(toolbar1,0,0)
mainLayout.addWidget(toolbar2,0,1)
mainLayout.addWidget(toolbar3,2,0)
mainLayout.addWidget(toolbar4,2,1)
mainLayout.addWidget(canvas1,1,0)
mainLayout.addWidget(canvas2,1,1)
mainLayout.addWidget(canvas3,3,0)
mainLayout.addWidget(canvas4,3,1)
self.setLayout(mainLayout)
self.setWindowTitle("Flow Layout")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())
I had a similar issue and I solved it by:
toolbar1.setMinimumWidth(canvas1.width())
toolbar2.setMinimumWidth(canvas2.width())
toolbar3.setMinimumWidth(canvas3.width())
toolbar4.setMinimumWidth(canvas4.width())
The problem is when the toolbar becomes wider than the canvas.
I know that plotly renders into HTML and can be embedded in web-like environments. I wonder if one can do that inside an HTML window in a PyQt application? Specifically I'd like to know if that works offline, having no internet connection.
EDIT:
This is an excerpt how I finally embedded graphs using matplotlib:
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg \
import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg \
import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
class Contour(QtGui.QFrame):
def __init__(self, parent=None):
super(Contour, self).__init__(parent)
self.parent = parent
# a figure instance to plot on
self.figure = plt.figure(figsize=(20, 30))
r, g, b = 100./255., 100./255., 100./255.
self.figure.patch.set_facecolor(color=(r, g, b))
# this is the Canvas Widget that displays the `figure`
# it takes the `figure` instance as a parameter to __init__
self.canvas = FigureCanvas(self.figure)
# this is the Navigation widget
# it takes the Canvas widget and a parent
self.toolbar = NavigationToolbar(self.canvas, self)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
And then in another function:
# create an axis
ax1 = self.figure.add_subplot(211, frame_on=False)
ax2 = self.figure.add_subplot(212, frame_on=False)
# plot data
r, g, b = 39./255., 40./255., 34./255.
ax1.plot(x, y, ls='o', color=(r, g, b), linewidth=3)
ax1.plot(coo[0], coo[1], 'go', zorder=20) # leading edge
ax1.plot(xg, yg, 'mo', zorder=30) # leading edge
ax1.plot(xr, yr, 'yo', zorder=30) # curvature circle center
ax1.add_patch(circle)
ax1.set_title('Contour', fontsize=14)
ax1.set_xlim(-10.0, 110.0)
# ax1.set_ylim(-10.0, 14.0)
r, g, b = 249./255., 38./255., 114./255.
ax1.fill(x, y, color=(r, g, b))
ax1.set_aspect('equal')
ax2.plot(coo[0], gradient, 'go-', linewidth=3)
ax2.set_title('Gradient', fontsize=14)
ax2.set_xlim(-10.0, 110.0)
You can use QWebEngineView from the QWebEngineWidgets module (I used PyQt5 here but I guess it'll also work for PyQt4). You create html code using plotly.offline.plot and set this as the html text for your instance of QWebEngineView. If you specify output_type='div' it will give you directly a string. I do not know why, but in my case it only worked with include_plotlyjs='cdn', but in that case you need to be on the internet for it to work according to the plotly documentation. By doing it that way the plot stays interactive also in your PyQt application.
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QMainWindow
from plotly.graph_objects import Figure, Scatter
import plotly
import numpy as np
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# some example data
x = np.arange(1000)
y = x**2
# create the plotly figure
fig = Figure(Scatter(x=x, y=y))
# we create html code of the figure
html = '<html><body>'
html += plotly.offline.plot(fig, output_type='div', include_plotlyjs='cdn')
html += '</body></html>'
# we create an instance of QWebEngineView and set the html code
plot_widget = QWebEngineView()
plot_widget.setHtml(html)
# set the QWebEngineView instance as main widget
self.setCentralWidget(plot_widget)
if __name__ == '__main__':
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
I once tried using the:
import plotly.offline as plt
.
.
.
plt.plot(fig, filename=testName + '__plot.html')
and then tried to generate a plot.. this gave me an HTML file which then I also tried putting on a QWebView as its URL property [just to see if it renders].
Here is the image for your reference:
Plotly is primarily developed to make graphing in a browser easy. I don't think it can be embedded into a UI framework like PyQT or Tkinter. Plotly has a enterprise version which works in our company network without a internet connection.
PyQtgraph or MatPlotLib may be an option for you if you really need to embedd graphs in PyQT.
Here's sample code to export graphs as png's and then embed png image into your PyQT application.
import matplotlib.pyplot as plt
plt.plot([1,2,3,4,5], [10,20,30])
plt.savefig('graphs.png')
import os,sys
from PyQt4 import QtGui
pic.setPixmap(QtGui.QPixmap("graphs.png"))
Not sure if this totally meets your need put given you have
import plotly.plotly as py
you can save the image and then
py.image.save_as({'data': data}, 'your_image_filename.png')
pic.setPixmap(QtGui.QPixmap("your_image_filename.png"))
Warning, I haven't tried this
I have written the following script which is creating an empty GUI with a button that calls a Matplotlib chart:
import sys
import os
from PyQt4 import QtGui
from PyQt4 import *
import matplotlib.pyplot as plt
class SmallGUI(QtGui.QMainWindow):
def __init__(self):
super(SmallGUI,self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(1010,800,1010,800)
self.setWindowTitle('Sample')
palette = QtGui.QPalette()
palette.setColor(QtGui.QPalette.Background,QtCore.Qt.white)
self.setPalette(palette)
#Chart button
self.MyButton = QtGui.QPushButton(self)
self.MyButton.setGeometry(QtCore.QRect(88,65,110,20))
self.MyButton.setText('Create chart')
###############
QtCore.QObject.connect(self.MyButton,QtCore.SIGNAL("clicked(bool)"),self.makeChart)
self.show()
def makeChart(self):
plt.plot([1,2,3,4])
plt.ylabel('some numbers')
plt.show()
def main():
app = QtGui.QApplication(sys.argv)
sampleForm = SmallGUI()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I am wondering if there is any way to embedd the chart into the GUI. In other words, is it possible to make the chart showing attached to the user interface rather than popping up a new TK window?