2021年5月4日星期二

How to put multiple separate graphs into one Tkinter window?

I have been running this script:

from threading import Thread  import serial  import time  import collections  import matplotlib.pyplot as plt  import matplotlib.animation as animation  import struct  import copy  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk  import tkinter as Tk  from tkinter.ttk import Frame    class serialPlot:      def __init__(self, serialPort='/dev/ttyACM0', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=4):          self.port = serialPort          self.baud = serialBaud          self.plotMaxLength = plotLength          self.dataNumBytes = dataNumBytes          self.numPlots = numPlots          self.rawData = bytearray(numPlots * dataNumBytes)          self.dataType = None          if dataNumBytes == 2:              self.dataType = 'h'     # 2 byte integer          elif dataNumBytes == 4:              self.dataType = 'f'     # 4 byte float          self.data = []          self.privateData = None          for i in range(numPlots):   # give an array for each type of data and store them in a list              self.data.append(collections.deque([0] * plotLength, maxlen=plotLength))          self.isRun = True          self.isReceiving = False          self.thread = None          self.plotTimer = 0          self.previousTimer = 0          # self.csvData = []            print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')          try:              self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4)              print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')          except:              print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')        def readSerialStart(self):          if self.thread == None:              self.thread = Thread(target=self.backgroundThread)              self.thread.start()              # Block till we start receiving values              while self.isReceiving != True:                  time.sleep(0.1)        def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber):          if pltNumber == 0:  # in order to make all the clocks show the same reading              currentTimer = time.perf_counter()              self.plotTimer = int((currentTimer - self.previousTimer) * 1000)     # the first reading will be erroneous              self.previousTimer = currentTimer          self.privateData = copy.deepcopy(self.rawData)    # so that the 3 values in our plots will be synchronized to the same sample time          timeText.set_text('' + str(self.plotTimer) + '')          data = self.privateData[(pltNumber*self.dataNumBytes):(self.dataNumBytes + pltNumber*self.dataNumBytes)]          value,  = struct.unpack(self.dataType, data)          self.data[pltNumber].append(value)    # we get the latest data point and append it to our array          lines.set_data(range(self.plotMaxLength), self.data[pltNumber])          lineValueText.set_text('[' + lineLabel + '] = ' + str(value))        def backgroundThread(self):    # retrieve data          time.sleep(1.0)  # give some buffer time for retrieving data          self.serialConnection.reset_input_buffer()          while (self.isRun):              self.serialConnection.readinto(self.rawData)              self.isReceiving = True              #print(self.rawData)        def sendSerialData(self, data):          self.serialConnection.write(data.encode('utf-8'))        def close(self):          self.isRun = False          self.thread.join()          self.serialConnection.close()          print('Disconnected...')          # df = pd.DataFrame(self.csvData)          # df.to_csv('/home/rikisenia/Desktop/data.csv')      class Window(Frame):      def __init__(self, figure, master, SerialReference):          Frame.__init__(self, master)          self.entry = []          self.setPoint = None          self.master = master        # a reference to the master window          self.serialReference = SerialReference      # keep a reference to our serial connection so that we can use it for bi-directional communicate from this class          self.initWindow(figure)     # initialize the window with our settings        def initWindow(self, figure):          self.master.title("Haptic Feedback Grasping Controller")          canvas = FigureCanvasTkAgg(figure, master=self.master)          toolbar = NavigationToolbar2Tk(canvas, self.master)          canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)            # create out widgets in the master frame          lbl1 = Tk.Label(self.master, text="Distance")          lbl1.pack(padx=5, pady=5)          self.entry = Tk.Entry(self.master)          self.entry.insert(0, '0')     # (index, string)          self.entry.pack(padx=5)          SendButton = Tk.Button(self.master, text='Send', command=self.sendFactorToMCU)          SendButton.pack(padx=5)        def sendFactorToMCU(self):          self.serialReference.sendSerialData(self.entry.get() + '%')     # '%' is our ending marker    def main():      # portName = 'COM5'      portName = '/dev/ttyACM0'      baudRate = 38400      maxPlotLength = 100     # number of points in x-axis of real time plot      dataNumBytes = 4        # number of bytes of 1 data point      numPlots = 1            # number of plots in 1 graph      s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots)   # initializes all required variables      s.readSerialStart()                                               # starts background thread        # plotting starts below      pltInterval = 50    # Period at which the plot animation updates [ms]      xmin = 0      xmax = maxPlotLength      ymin = -(1)      ymax = 200      fig = plt.figure()      ax = plt.axes(xlim=(xmin, xmax), ylim=(float(ymin - (ymax - ymin) / 10), float(ymax + (ymax - ymin) / 10)))      ax.set_title('Strain Gauge/ToF')      ax.set_xlabel("Time")      ax.set_ylabel("Force/Distance")        # put our plot onto Tkinter's GUI      root = Tk.Tk()      app = Window(fig, root, s)        lineLabel = ['W', 'X', 'Y', 'Z']      style = ['y-', 'r-', 'c-', 'b-']  # linestyles for the different plots      timeText = ax.text(0.70, 0.95, '', transform=ax.transAxes)      lines = []      lineValueText = []      for i in range(numPlots):          lines.append(ax.plot([], [], style[i], label=lineLabel[i])[0])          lineValueText.append(ax.text(0.70, 0.90-i*0.05, '', transform=ax.transAxes))      anim = animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabel, timeText), interval=pltInterval)    # fargs has to be a tuple        plt.legend(loc="upper left")      root.mainloop()   # use this instead of plt.show() since we are encapsulating everything in Tkinter        s.close()      if __name__ == '__main__':      main()    

A window shows up with no data passing through it even though I have 4 sensors that have data coming from an Arduino. The windows contains 1 graph with 4 plots in it currently. I want 4 graphs each with one plot. I have been using https://thepoorengineer.com/en/python-gui/ as a reference to make graphs within python. The code for the data transfer is within the link as well. I tried to combine his 2 different codes and debugging it to make 4 graphs each with one plot to work with one Tkinter GUI window but it doesn't work. I also get an error of TypeError: getSerialData() missing 1 required positional argument: 'pltNumber' . Not sure why I get this error if pltNumber is in the parentheses in the code. I'm a beginner at python. What should I change to make the code work?

Script that can generate 4 separate graphs each with one plot that are not within a Tkinter GUI(works with 4 sensors but I need them within a Tkinter window):

from threading import Thread  import serial  import time  import collections  import matplotlib.pyplot as plt  import matplotlib.animation as animation  import struct  import copy      class serialPlot:      def __init__(self, serialPort='/dev/ttyACM0', serialBaud=38400, plotLength=100, dataNumBytes=2, numPlots=1):          self.port = serialPort          self.baud = serialBaud          self.plotMaxLength = plotLength          self.dataNumBytes = dataNumBytes          self.numPlots = numPlots          self.rawData = bytearray(numPlots * dataNumBytes)          self.dataType = None          if dataNumBytes == 2:              self.dataType = 'h'     # 2 byte integer          elif dataNumBytes == 4:              self.dataType = 'f'     # 4 byte float          self.data = []          self.privateData = None     # for storing a copy of the data so all plots are synchronized          for i in range(numPlots):   # give an array for each type of data and store them in a list              self.data.append(collections.deque([0] * plotLength, maxlen=plotLength))          self.isRun = True          self.isReceiving = False          self.thread = None          self.plotTimer = 0          self.previousTimer = 0            print('Trying to connect to: ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')          try:              self.serialConnection = serial.Serial(serialPort, serialBaud, timeout=4)              print('Connected to ' + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')          except:              print("Failed to connect with " + str(serialPort) + ' at ' + str(serialBaud) + ' BAUD.')        def readSerialStart(self):          if self.thread == None:              self.thread = Thread(target=self.backgroundThread)              self.thread.start()              # Block till we start receiving values              while self.isReceiving != True:                  time.sleep(0.1)        def getSerialData(self, frame, lines, lineValueText, lineLabel, timeText, pltNumber):          if pltNumber == 0:  # in order to make all the clocks show the same reading              currentTimer = time.perf_counter()              self.plotTimer = int((currentTimer - self.previousTimer) * 1000)     # the first reading will be erroneous              self.previousTimer = currentTimer          self.privateData = copy.deepcopy(self.rawData)    # so that the 3 values in our plots will be synchronized to the same sample time          timeText.set_text('' + str(self.plotTimer) + '')          data = self.privateData[(pltNumber*self.dataNumBytes):(self.dataNumBytes + pltNumber*self.dataNumBytes)]          value,  = struct.unpack(self.dataType, data)          self.data[pltNumber].append(value)    # we get the latest data point and append it to our array          lines.set_data(range(self.plotMaxLength), self.data[pltNumber])          lineValueText.set_text('[' + lineLabel + '] = ' + str(value))        def backgroundThread(self):    # retrieve data          time.sleep(1.0)  # give some buffer time for retrieving data          self.serialConnection.reset_input_buffer()          while (self.isRun):              self.serialConnection.readinto(self.rawData)              self.isReceiving = True        def close(self):          self.isRun = False          self.thread.join()          self.serialConnection.close()          print('Disconnected...')      def makeFigure(xLimit, yLimit, title):      xmin, xmax = xLimit      ymin, ymax = yLimit      fig = plt.figure()      ax = plt.axes(xlim=(xmin, xmax), ylim=(int(ymin - (ymax - ymin) / 10), int(ymax + (ymax - ymin) / 10)))      ax.set_title(title)      ax.set_xlabel("Time")      ax.set_ylabel("Force/Distance")      return fig, ax      def main():      # portName = 'COM5'      portName = '/dev/ttyACM0'      baudRate = 38400      maxPlotLength = 100     # number of points in x-axis of real time plot      dataNumBytes = 4        # number of bytes of 1 data point      numPlots = 4            # number of plots in 1 graph      s = serialPlot(portName, baudRate, maxPlotLength, dataNumBytes, numPlots)   # initializes all required variables      s.readSerialStart()                                               # starts background thread        # plotting starts below      pltInterval = 50    # Period at which the plot animation updates [ms]      lineLabelText = ['W', 'X', 'Y', 'Z']      title = ['Strain Gauge 1 Force', 'Strain Gauge 2 Force', 'ToF 1 Distance', 'ToF 2 Distance']      xLimit = [(0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength), (0, maxPlotLength)]      yLimit = [(-1, 1), (-1, 1), (-1, 1), (-1, 1)]      style = ['y-', 'r-', 'g-', 'b-']    # linestyles for the different plots      anim = []      for i in range(numPlots):          fig, ax = makeFigure(xLimit[i], yLimit[i], title[i])          lines = ax.plot([], [], style[i], label=lineLabelText[i])[0]          timeText = ax.text(0.50, 0.95, '', transform=ax.transAxes)          lineValueText = ax.text(0.50, 0.90, '', transform=ax.transAxes)          anim.append(animation.FuncAnimation(fig, s.getSerialData, fargs=(lines, lineValueText, lineLabelText[i], timeText, i), interval=pltInterval))  # fargs has to be a tuple          plt.legend(loc="upper left")      plt.show()        s.close()      if __name__ == '__main__':      main()  
https://stackoverflow.com/questions/67390775/how-to-put-multiple-separate-graphs-into-one-tkinter-window May 05, 2021 at 03:11AM

没有评论:

发表评论