PySide2 updating a graph - python-3.x

I have been knocking my head against the wall on the following issue for quite some times now and need some fresh pair of eyes to help me out.
In Qt Designer I created a tab with a QComboBox (to select a feature), a QPushButton (to instruct the plotting of the feature) and a QWidget (plot area, called mywidget). The whole code is largely inspired from various codes found on SO.
In main.py I connected the QPushButton to the following function (defined within my QtApp class):
def launchGraph(self):
df1 = ... #data from a data source
self.mywidget.figure = Figure()
self.mywidget.canvas = FigureCanvas(self.mywidget.figure)
self.mywidget.toolbar = NavigationToolbar(self.mywidget.canvas, self)
self.mywidget.graphLayout = QtWidgets.QVBoxLayout()
self.mywidget.graphLayout.addWidget(self.mywidget.canvas)
self.mywidget.graphLayout.addWidget(self.mywidget.toolbar)
self.mywidget.setLayout(self.mywidget.graphLayout)
ax1f1 = self.mywidget.figure.add_subplot(111)
ax1f1.clear()
ax1f1.xaxis.set_major_formatter(mdates.DateFormatter('%b%-y'))
ax1f1.plot(df1['x'], df1['y'], linewidth=1, color='blue')
ax1f1.set(title='My Little Graph')
self.mywidget.canvas.draw()
The issue is that when I launched my window, select a feature and click the button, the correct graph is being shown. If I changed the feature and click the plot button, nothing happens. I did print the feature of the combobox and it prints the correct up-to-date value from the combobox however the graph is not replaced/updated. I also added a test-variable isgraph and used self.mywidget.figure.clear() but no success neither. canvas.repaint() doesn't update the graph neither. It feels like I need to use a test-variable to check whether a graph is there or not and if yes then I need to clen up the content of mywidget. But that seems overcomplicated for this issue (?)
For info I import the following:
from gui import main
from PySide2 import QtWidgets, QtCore, QtGui
from matplotlib.figure import Figure
import matplotlib.dates as mdates
from matplotlib.dates import DateFormatter
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
Edit:
Here is the minimal/adapted full code:
from gui import main
from PySide2 import QtWidgets, QtCore, QtGui
from matplotlib.figure import Figure
import matplotlib.dates as mdates
from matplotlib.dates import DateFormatter
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
class MyQtApp(main.Ui_MainWindow, QtWidgets.QMainWindow):
def __init__(self):
super(MyQtApp, self).__init__()
self.setupUi(self)
self.graphBtn.clicked.connect(self.launchGraph)
self.show()
def launchGraph(self):
if self.mycb.currrentText() == 'feature1':
df1 = ... #data from a data source
else: (#== feature2)
df1 = ... #some other data
self.mywidget.figure = Figure()
self.mywidget.canvas = FigureCanvas(self.mywidget.figure)
self.mywidget.toolbar =
NavigationToolbar(self.mywidget.canvas, self)
self.mywidget.graphLayout = QtWidgets.QVBoxLayout()
self.mywidget.graphLayout.addWidget(self.mywidget.canvas)
self.mywidget.graphLayout.addWidget(self.mywidget.toolbar)
self.mywidget.setLayout(self.mywidget.graphLayout)
ax = self.mywidget.figure.add_subplot(111)
ax.clear()
ax.plot(df1['x'], df1['y'])
self.mywidget.canvas.draw()
In Qt Designer (file main.ui comnverted into. main.py), I placed:
- one combobox, called mycb and having 2 values: [feature1, feature2]
- one push button, called graphBtn
- a simple and empty QWidget called mywidget

The problem is most likely, that when you run the launchGraph after the initial run, the function creates another ax1f1 underneath the initial one. Therefore the initial one keeps on showing and no errors are displayed.
In this particular case, you want to keep working with the initial ax1f1 instead of re-declaring another one.
Something like this could fix the problem:
def launchGraph(self):
if self.mycb.currrentText() == 'feature1':
df1 = ['some_data'] #data from a data source
else: (#== feature2)
df1 = ['some_other_data'] #some other data
self.mywidget.figure = Figure()
self.mywidget.canvas = FigureCanvas(self.mywidget.figure)
self.mywidget.toolbar = NavigationToolbar(self.mywidget.canvas, self)
self.mywidget.graphLayout = QtWidgets.QVBoxLayout()
self.mywidget.graphLayout.addWidget(self.mywidget.canvas)
self.mywidget.graphLayout.addWidget(self.mywidget.toolbar)
self.mywidget.setLayout(self.mywidget.graphLayout)
try:
self.ax1f1.clear()
except:
self.a1f1 = self.mywidget.figure.add_subplot(111)
self.ax1f1.clear()
self.ax1f1.xaxis.set_major_formatter(mdates.DateFormatter('%b%-y'))
self.ax1f1.plot(df1['x'], df1['y'], linewidth=1, color='blue')
self.ax1f1.set(title='My Little Graph')
self.mywidget.canvas.draw()

Related

Matplotlib, Tkinter, Serial Multiprocessing

I created a code (using Tkinter, Python3 and matplotlid) that could read data from different serial ports, save them to csv, then create graphs and finally preview data in GUI. The code was splited in two different scripts. The main script contained reading data, save data to csv an priview of data and the other script contained the graph creation.
Today I rewrote the code using the answer of #user2464430 here. The code is working, but I can't update the GUI. Opens once and then no refresh with new data.
The following code is a part of total code.
My code is:
from PIL import ImageTk, Image
import tkinter as Tk
import multiprocessing
from queue import Empty, Full
from time import strftime
import serial
import numpy as np
import matplotlib.pyplot as plt
from drawnow import *
from pylab import *
import pandas as pd
from datetime import timedelta
from datetime import datetime
import plotly.graph_objs as go
from plotly.subplots import make_subplots
import locale
import os
class GuiApp(object):
def __init__(self, image):
self.root = Tk.Tk()
self.root.resizable(width=False, height=False)
self.root.geometry("1600x800+0+0")
C = Canvas(self.root, bg="black", width=1600, height=800)
def BasicLabels():
....... # in this stage create multiple axis labels
Î¥AxisLabels()
BasicLabels()
def ValueLabels():
....... # Read and munipulate datas from CSV file and print in in labels
ValueLabels()
C.pack()
def GenerateData(q): #Read Serial Ports and store data to CSV file
file_exists = os.path.isfile("BigData.csv")
header = [["Daytime,T1"]]
if not file_exists:
with open("BigData.csv", "a+") as csvfile:
np.savetxt(csvfile, header, delimiter=",", fmt="%s", comments="")
while True:
try:
ser1 = serial.Serial(port="COM4", baudrate=9600)
read_ser1 = ser1.readline()
if read_ser1 == "":
read_ser1 = "Missing Value"
else:
read_ser1 = ser1.readline()
read_ser1 = str(read_ser1[0 : len(read_ser1)].decode("utf-8"))
# print("COM4:", read_ser1)
ser1.close()
except:
print("Failed 1")
read_ser1 = "9999,9999,9999,9999,9999"
daytime = strftime(" %d-%m-%Y %H:%M:%S")
rows = [
daytime
+ ","
+ read_ser1.strip()
]
with open("BigData.csv", "a+") as csvfile:
np.savetxt(csvfile, rows, delimiter=",", fmt="%s", comments="")
CreateGraphs()
def CreateGraphs():
#Code to generate graph. Called every time i have new line in CSV.
if __name__ == "__main__":
# Queue which will be used for storing Data
q = multiprocessing.Queue()
q.cancel_join_thread() # or else thread that puts data will not term
gui = GuiApp(q)
t1 = multiprocessing.Process(target=GenerateData, args=(q,))
t1.start()
gui.root.mainloop()
t1.join()
The graphs are generating after while True in GenerateData.
All datas for labels and graphs are coming from CSV file and not directly from serial port.
Is it possible to update GUI with latest datas from CSV and created graphs?
Thank for your time.

Python Auto Click bot not working and I cant figure out why

I made a python 'aimbot' type program that looks for targets in the aim trainer and clicks on them. The problem is it clicks but not on the target and I'm pretty sure I did everything right. Here's the program:
from pyautogui import *
import pyautogui
import time
import keyboard
import random
import win32api, win32con
time.sleep(2)
def click(x,y):
win32api.SetCursorPos((x,y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
while keyboard.is_pressed('q')== False:
pic = pyautogui.screenshot(region=(0,0,1080,1920))
width, height = pic.size
for x in range(0,width,5):
for y in range(0,height,5):
r,g,b = pic.getpixel((x,y))
if g == 154:
click(x,y)
time.sleep(1)
break

How can I get the individual points and their attributes from a scatterplot in pyqtgraph?

I was able to create a ScatterPlotItem in pyqtgraph without a hitch by promoting a Graphics View widget to a PlotWidget in Qt Designer. I plotted some random data on it and now I want to access the individual points I click on. The docs say that one can connect the sigClicked(self, points) signal, which, in theory, should return the points under the cursor. But that does not seem to be the case, because when I click on a point I get the same object regardless of which point I clicked. I suspect that this signal returns the entire ScatterPlotItem and not any specific point.
Here is my code so far:
import sys, time
from timeit import default_timer as timer
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSlot, Qt, QPoint, QUrl, QEvent
from PyQt5.QtWidgets import *
from PyQt5 import QtMultimedia
from PyQt5.uic import loadUi
import pyqtgraph as pg
import numpy as np
class ScatterExample(QMainWindow):
def __init__(self):
# Main Loop
super(ScatterExample, self).__init__()
loadUi('<path/to/ui file>.ui', self)
self.setWindowTitle('ScatterExample')
self.scatter = pg.ScatterPlotItem(pxMode=False, pen=pg.mkPen(width=1, color='g'), symbol='t', size=1)
self.scatter.sigClicked.connect(self.onPointsClicked)
self.Scatter_Plot_View.addItem(self.scatter) # Scatter_Plot_View is the Graphics View I promoted to PlotWidget
n = 5
print('Number of points: ' + str(n))
data = np.random.normal(size=(2, n))
pos = [{'pos': data[:, i]} for i in range(n)]
now = pg.ptime.time()
self.scatter.setData(pos)
print(self.scatter.data)
def onPointsClicked(self, points):
print('Ain\'t getting individual points ', points)
points.setPen('b', width=2) # this turns EVERY point blue, not just the one clicked.
The above print statement prints:
Ain't getting individual points <pyqtgraph.graphicsItems.ScatterPlotItem.ScatterPlotItem object at 0x000001C36577F948>
How can I get the points I click on and their corresponding attributes, such as x and y coordinates?
As eyllansec was kind enough to suggest, I changed my def onPointsClicked(self, points): to def onPointsClicked(self, obj, points): and now pyqtgraph works a expected.

PyQT5 slot with Matplotlib has no effect

I'm trying to implement a slot method that will clear a matplotlib figure that is part of QtWidget. I have tried with both Python 3.6 and 3.5 on windows 7 and 10 and i get same behaviour.
My issue is that i can perfectly clear the figure when calling the wipe method from the main block of code or within the class drawing the figure. But whenever this very same method is called as a slot to a PyQT signal, slot is actually activated but the call to fig.clf() does not clear the figure.
The code below shows the issue :
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QApplication,QMainWindow, QSizePolicy,QWidget,QVBoxLayout, QPushButton
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class GraphPopulate(FigureCanvas):
def __init__(self, parent=None):
self.fig = Figure(figsize=(6,4))
self.ax = self.fig.add_axes([0.07, 0.16, 0.95, 0.95]) # fraction of figure size #
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
x = [2000, 2001, 2002, 2003, 2004]
y = [10, 20, 30, 40, 50]
self.plt = self.ax.plot(x, y)
# self.wipe() # OK This one works as well
# self.plt.close(self.fig) # close the app, does not only clear figure
def wipe(self):
print('wipe start')
# del self.fig # close the app on second call, does not only clear figure !!
# self.plt.close(fig) # close the app, does not only clear figure
rc = self.fig.clf()
print('return code from clf() '+ str(rc))
print('wipe stop')
if __name__ == '__main__':
# create main window
app = QApplication(sys.argv)
MainWindow = QMainWindow()
MainWindow.resize(800, 600)
# create widget to be used by MathPlotlib
WidgetMatplot = QWidget(MainWindow)
WidgetMatplot.setGeometry(QRect(10, 40, 500, 500))
# create Push Button with clicked signal linked to GraphPopulate.wipe() slot
button = QPushButton(MainWindow)
button.setText("Push Me !")
# add widget to vertical box layout
hbox = QVBoxLayout(MainWindow)
hbox.addWidget(WidgetMatplot)
hbox.addWidget(button)
g = GraphPopulate(WidgetMatplot)
button.pyqtConfigure(clicked=g.wipe) # NOT OK !!!!!!! g.wipe is triggered as prints statements show
# on console but self.fig.clf() has no effect
MainWindow.show()
# g.wipe() # OK
sys.exit(app.exec_())
I've put comments on statements that if uncommented are actually clearing the figure or closing the whole app.
Looks like the "matplotlib context" is not the same when called from the signa-slot connect than from other calls.
May be i miss something trivial. If so sorry for that but i couldn't find any direction to investigate by myself...so i rely on you guys to spot it out.
Thanks - Thibault
In your code the figure is successfully cleared. You just don't see it because nobody told the figure that it needs to be redrawn.
If you redraw the figure, you'll see that it is indeed empty.
def wipe(self):
self.fig.clf()
self.fig.canvas.draw_idle()

Incorrect behaviour of print() when executed from within a QTDialog window in Spyder

I am working on a very simple interface to explore/graph csv files. My aim is ultimately to explore, not to build software as I am not a developer, more of a "desperate user" :-)
I am leveraging the code found in this example
These are my first steps both in Python and in GUI, so I tend to put print messages in my calls so that I can more or less track what is happening. And this is where I found a strange behavior if I run the code from within Spyder.
import sys
import os
from PyQt4 import QtGui
import pandas as pd
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
# QtGui.QDialog
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = plt.figure()
# 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)
# Just some extra button to mess around
self.button= QtGui.QPushButton('Push Me')
self.button.clicked.connect(self.do_print)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def do_print(self):
print('Hello World!!')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The strange behavior is that if I push the button once, nothing happens on the Ipython console. By the second time I push, then two "Hello World!" printouts appear.
If, on the other hand, I just launch my script from within a Windows Shell:
python my_simple_test.py
Then everything works as expected.
What am I then doing wrong from within Spyder?
Thanks,
Michele
IPython buffers stdout a bit differently from a terminal. When something is printed, it looks at how long it has been since it last flushed the buffer, and if it's longer than some threshold, it flushes it again. So the second time you click the button, it flushes stdout, and you see both outputs.
You can force it to flush immediately like this:
print('Hello World!!')
sys.stdout.flush()

Resources