Plotting data from the file kills GUI - python

I try to draw a graph from the data I have in the file. For example, a set of data with several chart points is no problem and it is drawn. However, the amount of data I have to draw is constantly growing and it is currently about 15000 points. When I try to load and draw them, the application interface crashes. My code is below. The data file is here: testdata.txt Could you please tell me how to deal with it?
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QDialog):
def __init__(self):
super().__init__()
title = "Wykresy"
self.setWindowTitle(title)
# 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 button connected to 'plot' method
self.button = QPushButton('Plot')
# adding action to the button
self.button.clicked.connect(self.plot)
# creating a Vertical Box layout
layout = QVBoxLayout()
# adding tool bar to the layout
layout.addWidget(self.toolbar)
# adding canvas to the layout
layout.addWidget(self.canvas)
# adding push button to the layout
layout.addWidget(self.button)
# setting layout to the main window
self.setLayout(layout)
self.showMaximized()
def plot(self):
with open('testdata.txt') as f:
lines = f.readlines()
x = [line.split('\t')[0] for line in lines]
y = [line.split('\t')[1] for line in lines]
# clearing old figure
self.figure.clear()
# create an axis
ax = self.figure.add_subplot(111)
# plot data
ax.plot(x, y, c = 'r', label = 'temperature')
self.figure.autofmt_xdate()
# refresh canvas
self.canvas.draw()
# driver code
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
# loop
sys.exit(app.exec_())

The main bottleneck seems to be the autofmt_xdate() call, which adds a date label for every one of those 15k points. This happens because your x labels aren't actually dates; as far as pyplot is concerned, they're just arbitrary strings, so it doesn't know which ones to keep and which ones to throw away. Something similar is happening for the y labels too.
To parse x into datetime objects and y into floats:
from datetime import datetime
...
x = [datetime.strptime(line.split('\t')[0], '%Y-%m-%d %H:%M:%S') for line in lines]
y = [float(line.split('\t')[1]) for line in lines]
Now I get a single tick per hour on the x axis, and one per 2.5 degrees on the y axis. Rendering is nearly instantaneous.
You should also consider downsampling your data before trying to plot it. 15000 points is way more than fits in the horizontal resolution of a typical computer screen anyway.

To add to #Thomas' answer, you could use pandas to read the file, which may be faster than looping through the content.
(...)
def plot(self):
df = pd.read_csv('testdata.txt', sep='\t', header=None, parse_dates=[0])
(...)
# plot data
ax.plot(df[0], df[1], c='r', label='temperature')

Related

NavigationToolbar2QT switching line colors when adding a title to plot

I am trying to build a GUI in pyqt5 that loads a data file, then using a dropdown menu the user can select the data to plot.
Since I am new to pyqt5 I followed the example given here:
Display a graph on selecting a combobox option on PyQt GUI , over an existing mainwindow
Then I added the NavigationToolbar2QT to give the user the capability to modify the curves and plot, e.g: Include a title, change line colors, modify x/y axis labels, modify legends, etc.
However, for a 60,000 lines file when I plot 3 lines and add a new title to the plot or change the marker, all lines switch colors unexpectedly and the plot is updated giving the wrong information because the legends remain the same. Let me indicate that I don't do anything manually to update the line colors as that isn't my intention. As an example:
When I open the file and plot 3 lines for the first time, such that:
A vs time is blue - bottom line - legend: "line A"
B vs time is orange - middle line - legend: "line B"
C vs time is green - top line - legend: "line C"
If I then proceed to add a new title to the plot, using the toolbar, then:
A vs time is green - bottom line - legend: "line C"
B vs time is blue - middle line - legend: "line A"
C vs time is orange - top line - legend: "line B"
Is there a better way to plot lines with pyqt5 and also have the capability to modify the plot title and the line markers? Why is this happening? .
The code is below: (EDITED)
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import pandas as pd
import os
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.combo = QtWidgets.QComboBox()
self.combo.addItem("Choose a field")
self.button = QtWidgets.QPushButton('Read csv file')
# axes and widget for plotting and displaying the figure
self.fig, self.ax = plt.subplots()
self.figure_widget = FigureCanvas(self.fig)
self.toolbar = NavigationToolbar(self.figure_widget,self)
plt.tight_layout()
# set up layout
vlayout = QtWidgets.QVBoxLayout(self)
hlayout = QtWidgets.QHBoxLayout()
hlayout.addWidget(self.combo)
hlayout.addWidget(self.button)
hlayout.addStretch(2)
vlayout.addLayout(hlayout)
vlayout.addWidget(self.toolbar)
vlayout.addWidget(self.figure_widget)
self.button.clicked.connect(self.read_data)
self.combo.currentTextChanged.connect(self.field_changed)
def read_data(self):
dialog = QtWidgets.QFileDialog(self, directory=os.curdir, caption='Open data file' )
dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptOpen)
dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
if dialog.exec():
# read data and add columns to combo box
file = dialog.selectedFiles()[0]
self.data = pd.read_csv(file)
self.combo.clear()
self.combo.addItem("Choose a field")
self.time_column_name = list(self.data.keys())[0]
self.time_column = self.data[self.time_column_name]
for field in self.data.columns:
if field != self.time_column_name:
self.combo.addItem(field)
self.combo.setCurrentIndex(0)
def field_changed(self, field):
self.ax.clear()
if field in self.data.columns:
self.data.plot(x=self.time_column_name,y=field, ax=self.ax)
self.ax.set_ylabel(field)
self.ax.set_xlabel('index')
plt.tight_layout()
self.fig.canvas.draw()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = Widget()
widget.show()
app.exec()
Example data file format:
1 time A B C
2 0.000025 32.0789999 45.0986766 74.054567433
3 0.000050 33.0548333 47.2343400 76.038787222
4
.
.
.
.
.

How do I make QDockWidget expand to fill all the available space? And why can't I put more than two docks side by side?

I am creating an application in pyqt5 to plot multiple figures side by side and above/below each other, and I want to make the plots dockable. The result is not behaving the way I want/would expect and after a long time of searching the internet for answers/solutions I still don't know why.
So far this is my code:
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5 import QtWidgets, QtCore
import traceback
import logging
def exception_hook(exc_type, exc_value, exc_tb):
tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
logging.error('\n' + tb)
QtWidgets.QApplication.quit()
class PlotWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(PlotWindow, self).__init__(parent=parent)
self.setWindowTitle('plot window')
self.setCentralWidget(QtWidgets.QWidget(self))
def add_figure(self, figure):
mplCanvas = FigureCanvas(figure)
mplCanvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
dockableMplCanvas = QtWidgets.QDockWidget(self)
dockableMplCanvas.setWidget(mplCanvas)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dockableMplCanvas)
if __name__ == '__main__':
import sys
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('matplotlib').setLevel(logging.ERROR)
sys.excepthook = exception_hook
app = QtWidgets.QApplication(sys.argv)
pw = PlotWindow()
for _ in range(4):
fig = Figure(tight_layout=True)
ax = fig.add_subplot()
pw.add_figure(fig)
pw.show()
exit(app.exec_())
It results in the following:
If I now rearrange the plots by first putting one of them on the left of the other three:
..And then putting one underneath the one on the left:
Now if I rescale the window then the space between the two columns of plots expands but the plots don't expand to fill the space:
Why is that? and how can I fix it?
The central widget is resizing to fill the space between the two columns of plots, the solution is to add:
self.centralWidget().setFixedSize(0, 0)
to prevent the central widget from expanding

How can I show matplotlib on my PyQt5 Qwidget

I want to draw on my PyQt designer file.
I made 2 py file, one is Main, and another one is ui file(pyuic)
this is code of UI
self.graph_widget = QtWidgets.QWidget(self.tab_4)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.graph_widget.sizePolicy().hasHeightForWidth())
self.graph_widget.setSizePolicy(sizePolicy)
self.graph_widget.setObjectName("graph_widget")
graph_widget is widget name
def show_graph(self):
self.graph_widget.fig = plt.Figure()
self.graph_widget.canvas = FigureCanvas(self.graph_widget.fig)
canvasLayout = QVBoxLayout()
canvasLayout.addStretch(1)
self.graph_widget.layout = QHBoxLayout()
self.graph_widget.layout.addLayout(canvasLayout)
ax = self.graph_widget.fig.add_subplot(1, 1, 1)
ax.grid()
self.graph_widget.canvas.draw()
This is code of Main for show graph on my widget.
I want to show graph on my widget, but it doesn't work.
just show white window as before send signal.
and doesn't print any error.
please let me know how I print it.
I think you don't understand well the concept of objects.
In your function show_graph(), you wrote self.graph_widget.fig, that means, fig is an attribute (a variable) of the object graph_widget, which is an object QWidget, so by writing self.graph_widget.fig = plt.Figure() has no sense.
I suggest you this solution:
def show_graph(self):
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas #You can put it at the beginning of your program
self.fig = plt.Figure()
self.plot = self.fig.add_subplot()
self.canvas = FigureCanvas(self.fig)
self.canvas.draw()
#Create a layout
layout = QVBoxLayout()
layout.addWidget(self.canvas)
#You can now add your layout to your QWidget()
self.graph_widget.setLayout(layout)
#You can active the grid by the following line
self.plot.yaxis.grid()
Sorry for my English, I am French.

Live graph with ultrasonic sensor on arduino and matplotlib pyqt to show graph

I stuck on my code. I want to have GUI on PyQt5 to show my graph. I have working code in matplot, but I cannot transform it to PyQT. I'm a beginner at this. I tried to find working similar code but I only did this. This works on matplotlib, but i want to have it on my GUI.
import serial
import numpy
import matplotlib.pyplot as plt
from drawnow import *
distance =[]
arduinoData=serial.Serial('COM4',115200)
plt.ion()
cnt=0
def makeFig():
plt.ylim(2,20)
plt.title('Plot ')
plt.grid(True)
plt.ylabel('Distance cm')
plt.plot(distance, 'ro-', label='Distance ')
plt.legend(loc='upper left')
while True: # While loop that loops forever
while (arduinoData.inWaiting()==0): #Wait here until there is data
pass #do nothing
arduinoString = arduinoData.readline() #read the line of text from the serial port
measurement = int(arduinoString) #Convert first element to floating number and put in measurement
distance.append(measurement) #Build our distance array by appending temp readings
drawnow(makeFig) #Call drawnow to update our live graph
plt.pause(.000001) #Pause Briefly. Important to keep drawnow from crashing
cnt=cnt+1
if(cnt>50): #If you have 50 or more points, delete the first one from the array
distance.pop(0) #This allows us to just see the last 50 data points
#This allows us to just see the last 50 data points
My PyQt and Matplot. I want to click on the button and show this graph.
I got error that " in line 51, in plot
measurement=int(arduinoString)
ValueError: invalid literal for int() with base 10: b'\n' " and
"Backend TkAgg is interactive backend. Turning interactive mode on."
I'm sending data that are simple distance like 5, 10 and so on from my sensor.
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import random
import serial
import numpy
from drawnow import *
arduinoData=serial.Serial('COM4',115200)
cnt=0
distance =[]
class Window(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 button connected to `plot` method
self.button = QPushButton('Plot')
self.button.clicked.connect(self.plot)
# set the layout
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def plot(self):
while True:
while (arduinoData.inWaiting()==0): #wait for data
pass
arduinoString=arduinoData.readline() #read the line of text from the serial port
measurement=int(arduinoString)
distance.append(measurement)
plt.pause(0.00001)
global cnt
cnt=cnt+1
if (cnt>50):
distance.pop(0)
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.plot(distance, '*-')
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
while True: # While loop that loops forever
while (arduinoData.inWaiting()==0): #Wait here until there is data
pass #do nothing
This doesn't actually wait, it executes whatever's below it regardless of your condition check. Try it as follows:
while True:
if (arduinoData.inWaiting()==0):
continue
#rest of your code

How to plot different graphs in different canvas in PyQt4 in a window?

I am trying to code with python3 a GUI that plots 4 different graph in 4 respective layout : speed, height, coordinates and the angle. Right now I am able to draw the figure in each respective layout. However, I have no idea how to plot different function into each graph. I used a method to randomly generate 10 points to plot. When the method is called, it plots the same graph into each 4 canvas.
So my question is **if there is anyway to plot different function respectively to a figure(one plot per graph)?**I am pretty new to python3 and would be grateful for any help provided.
If possible, I would like to avoid using many subplots in a figure since a layout for each figures exist already.
Here is what the current GUI looks like when I call the test method that generates random points, you can see that
the same graph are plotted in each canvas
I will also mention, if it adds any constraints, that this code will eventually be used to plots graph that will update with data coming from another thread.
And here's the code:
from PyQt4 import QtGui
from .flight_dataUI import Ui_Dialog
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import random
class FlightData(QtGui.QDialog, Ui_Dialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.figure = plt.figure() # FlightData.figure = matplotlib.pyplot.figure()
# Creates a figure widget self.name = FigureCanvas(self.figure)
self.speedGraph = FigureCanvas(self.figure)
self.heightGraph = FigureCanvas(self.figure)
self.mapGraph = FigureCanvas(self.figure)
self.angleGraph = FigureCanvas(self.figure)
# -------------------------------------------------------------
self.speedLayout.addWidget(self.speedGraph) # insert widget "speedGraph" in speedLayout
self.heightLayout.addWidget(self.heightGraph) # insert widget "heightGraph" in heightLayout
self.mapLayout.addWidget(self.mapGraph) # insert widget "mapGraph" in mapLayout
self.angleLayout.addWidget(self.angleGraph) # insert widget "angleGraph" in angleLayout
self.ax = self.figure.add_subplot(111)
self.ax.hold(False)
self.init_widgets()
def init_widgets(self):
self.analyseButton.clicked.connect(self.open_analysedata)
self.draw_plot()
def open_analysedata(self):
self.done(2) # Closes and delete Dialog window et and return the int 2 as a results in main_window.py
def draw_plot(self):
data = [random.random() for i in range(10)]
self.ax.plot(data, '-*')
If embedding do not import pyplot, it's global state will only cause you trouble. You are using the same figure to initialize all 4 FigureCanvas objects. You want to do something like:
from matplotlib.figure import Figure
class FlightData(QtGui.QDialog, Ui_Dialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.setupUi(self)
self.figs = {}
self.canvas = {}
self.axs = {}
plot_names = ['speed', 'height', 'map', 'angle']
for pn in plot_names:
fig = Figure()
self.canvas[pn] = FigureCanvas(fig)
ax = fig.add_subplot(1, 1, 1)
self.figs[pn] = fig
self.axs[pn] = ax
# -------------------------------------------------------------
self.speedLayout.addWidget(self.canvas['speed'])
self.heightLayout.addWidget(self.canvas['height'])
self.mapLayout.addWidget(self.canvas['map'])
self.angleLayout.addWidget(self.canvas['angle'])
def draw_plot(self, target, data):
self.axs[target].plot(data, '-*')
self.canvas[target].draw_idle()

Categories

Resources